fix: adding ai assistance to pages (#2905)

* fix: adding ai modal to pages

* fix: pages overflow

* chore: update pages UI

* fix: updating page description while using ai assistance

* fix: gpt assistant modal height and position

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
This commit is contained in:
sriram veeraghanta 2023-11-27 20:39:18 +05:30
parent 10cde58363
commit d84e043c93
7 changed files with 134 additions and 80 deletions

View File

@ -46,7 +46,7 @@ export const EditorHeader = (props: IEditorHeader) => {
return (
<div className="flex items-center border-b border-custom-border-200 py-2 px-5">
<div className="flex-shrink-0 w-56 lg:w-80">
<div className="flex-shrink-0 w-56 lg:w-72">
<SummaryPopover
editor={editor}
markings={markings}

View File

@ -7,7 +7,7 @@ export const HeadingComp = ({
}) => (
<h3
onClick={onClick}
className="ml-4 mt-3 cursor-pointer text-sm font-bold font-medium leading-[125%] tracking-tight hover:text-custom-primary max-md:ml-2.5"
className="ml-4 mt-3 cursor-pointer text-sm font-medium leading-[125%] tracking-tight hover:text-custom-primary max-md:ml-2.5"
role="button"
>
{heading}

View File

@ -19,7 +19,7 @@ export const PageRenderer = (props: IPageRenderer) => {
return (
<div className="w-full pl-7 pt-5 pb-64">
<h1 className="text-4xl font-bold break-all pr-5 -mt-2">
<h1 className="text-4xl font-bold break-words pr-5 -mt-2">
{documentDetails.title}
</h1>
<div className="flex flex-col h-full w-full pr-5">

View File

@ -15,7 +15,7 @@ export const SummarySideBar = ({
}: ISummarySideBarProps) => {
return (
<div
className={`h-full px-5 pt-5 transition-all duration-200 transform overflow-hidden ${
className={`h-full p-5 transition-all duration-200 transform overflow-hidden ${
sidePeekVisible ? "translate-x-0" : "-translate-x-full"
}`}
>

View File

@ -127,14 +127,14 @@ const DocumentEditor = ({
documentDetails={documentDetails}
/>
<div className="h-full w-full flex overflow-y-auto">
<div className="flex-shrink-0 h-full w-56 lg:w-80 sticky top-0">
<div className="flex-shrink-0 h-full w-56 lg:w-72 sticky top-0">
<SummarySideBar
editor={editor}
markings={markings}
sidePeekVisible={sidePeekVisible}
/>
</div>
<div className="h-full w-full">
<div className="h-full w-[calc(100%-14rem)] lg:w-[calc(100%-18rem-18rem)]">
<PageRenderer
editor={editor}
editorContentCustomClassNames={editorContentCustomClassNames}
@ -142,7 +142,7 @@ const DocumentEditor = ({
documentDetails={documentDetails}
/>
</div>
<div className="hidden lg:block flex-shrink-0 w-56 lg:w-80" />
<div className="hidden lg:block flex-shrink-0 w-56 lg:w-72" />
</div>
</div>
);

View File

@ -117,10 +117,11 @@ export const GptAssistantModal: React.FC<Props> = (props) => {
return (
<div
className={`absolute ${inset} z-20 w-full space-y-4 rounded-[10px] border border-custom-border-200 bg-custom-background-100 p-4 shadow ${
className={`absolute ${inset} z-20 w-full flex flex-col space-y-4 rounded-[10px] border border-custom-border-200 bg-custom-background-100 p-4 shadow overflow-hidden ${
isOpen ? "block" : "hidden"
}`}
>
<div className="max-h-72 overflow-y-auto space-y-4 vertical-scroll-enable">
{((content && content !== "") || (htmlContent && htmlContent !== "<p></p>")) && (
<div className="text-sm">
Content:
@ -146,9 +147,11 @@ export const GptAssistantModal: React.FC<Props> = (props) => {
)}
{invalidResponse && (
<div className="text-sm text-red-500">
No response could be generated. This may be due to insufficient content or task information. Please try again.
No response could be generated. This may be due to insufficient content or task information. Please try
again.
</div>
)}
</div>
<Controller
control={control}
name="task"

View File

@ -2,17 +2,21 @@ import React, { useEffect, useRef, useState, ReactElement } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { Controller, useForm } from "react-hook-form";
import { Sparkle } from "lucide-react";
import { observer } from "mobx-react-lite";
// services
import { PageService } from "services/page.service";
import { FileService } from "services/file.service";
// hooks
import useUser from "hooks/use-user";
import { useDebouncedCallback } from "use-debounce";
import { useMobxStore } from "lib/mobx/store-provider";
// layouts
import { AppLayout } from "layouts/app-layout";
// components
import { PageDetailsHeader } from "components/headers/page-details";
import { EmptyState } from "components/common";
import { GptAssistantModal } from "components/core";
// ui
import { DocumentEditorWithRef, DocumentReadOnlyEditorWithRef } from "@plane/document-editor";
import { Spinner } from "@plane/ui";
@ -30,18 +34,23 @@ import { PAGE_DETAILS } from "constants/fetch-keys";
const fileService = new FileService();
const pageService = new PageService();
const PageDetailsPage: NextPageWithLayout = () => {
const PageDetailsPage: NextPageWithLayout = observer(() => {
const editorRef = useRef<any>(null);
// states
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
const [gptModalOpen, setGptModal] = useState(false);
// store
const {
appConfig: { envConfig },
} = useMobxStore();
// router
const router = useRouter();
const { workspaceSlug, projectId, pageId } = router.query;
const { user } = useUser();
const { handleSubmit, reset, getValues, control } = useForm<IPage>({
defaultValues: { name: "" },
const { handleSubmit, reset, getValues, control, setValue, watch } = useForm<IPage>({
defaultValues: { name: "", description_html: "<p></p>" },
});
// =================== Fetching Page Details ======================
@ -167,6 +176,22 @@ const PageDetailsPage: NextPageWithLayout = () => {
}
};
const handleAiAssistance = async (response: string) => {
if (!workspaceSlug || !projectId || !pageId) return;
const newDescription = `${watch("description_html")}<p>${response}</p>`;
setValue("description_html", newDescription);
editorRef.current?.setEditorValue(newDescription);
pageService
.patchPage(workspaceSlug.toString(), projectId.toString(), pageId.toString(), {
description_html: newDescription,
})
.then(() => {
mutatePageDetails((prevData) => ({ ...prevData, description_html: newDescription } as IPage), false);
});
};
useEffect(() => {
if (!pageDetails) return;
@ -227,6 +252,7 @@ const PageDetailsPage: NextPageWithLayout = () => {
}
/>
) : (
<div className="h-full w-full relative overflow-hidden">
<Controller
name="description_html"
control={control}
@ -266,6 +292,31 @@ const PageDetailsPage: NextPageWithLayout = () => {
/>
)}
/>
{projectId && envConfig?.has_openai_configured && (
<>
<button
type="button"
className="flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90 absolute top-3 right-[68px]"
onClick={() => setGptModal((prevData) => !prevData)}
>
<Sparkle className="h-4 w-4" />
AI
</button>
<GptAssistantModal
isOpen={gptModalOpen}
handleClose={() => {
setGptModal(false);
}}
inset="top-9 right-[68px] !w-1/2 !max-h-[50%]"
content=""
onResponse={(response) => {
handleAiAssistance(response);
}}
projectId={projectId.toString()}
/>
</>
)}
</div>
)}
</div>
</div>
@ -276,7 +327,7 @@ const PageDetailsPage: NextPageWithLayout = () => {
)}
</>
);
};
});
PageDetailsPage.getLayout = function getLayout(page: ReactElement) {
return (