chore: minor fixes on pages (#557)

* feat: block sync

* chore: minor fixes on pages

* fix: remove dangerously set inner html

* fix: pages crud operations mutation

* fix: favorites mutation for recent pages

* fix: remove dangerously set inner html
This commit is contained in:
Aaryan Khandelwal 2023-03-29 00:20:00 +05:30 committed by GitHub
parent c0a471e916
commit b654d30aeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 139 additions and 70 deletions

View File

@ -1,6 +1,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import dynamic from "next/dynamic";
// react-hook-form // react-hook-form
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
@ -16,6 +17,7 @@ type Props = {
handleClose: () => void; handleClose: () => void;
inset?: string; inset?: string;
content: string; content: string;
htmlContent?: string;
onResponse: (response: string) => void; onResponse: (response: string) => void;
}; };
@ -24,11 +26,16 @@ type FormData = {
task: string; task: string;
}; };
const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), {
ssr: false,
});
export const GptAssistantModal: React.FC<Props> = ({ export const GptAssistantModal: React.FC<Props> = ({
isOpen, isOpen,
handleClose, handleClose,
inset = "top-0 left-0", inset = "top-0 left-0",
content, content,
htmlContent,
onResponse, onResponse,
}) => { }) => {
const [response, setResponse] = useState(""); const [response, setResponse] = useState("");
@ -62,15 +69,6 @@ export const GptAssistantModal: React.FC<Props> = ({
const handleResponse = async (formData: FormData) => { const handleResponse = async (formData: FormData) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
if (!content || content === "") {
setToastAlert({
type: "error",
title: "Error!",
message: "Please enter some description to get AI assistance.",
});
return;
}
if (formData.task === "") { if (formData.task === "") {
setToastAlert({ setToastAlert({
type: "error", type: "error",
@ -82,11 +80,11 @@ export const GptAssistantModal: React.FC<Props> = ({
await aiService await aiService
.createGptTask(workspaceSlug as string, projectId as string, { .createGptTask(workspaceSlug as string, projectId as string, {
prompt: content, prompt: content && content !== "" ? content : "",
task: formData.task, task: formData.task,
}) })
.then((res) => { .then((res) => {
setResponse(res.response); setResponse(res.response_html);
setFocus("task"); setFocus("task");
if (res.response === "") setInvalidResponse(true); if (res.response === "") setInvalidResponse(true);
@ -105,12 +103,28 @@ export const GptAssistantModal: React.FC<Props> = ({
}`} }`}
> >
<form onSubmit={handleSubmit(handleResponse)} className="space-y-4"> <form onSubmit={handleSubmit(handleResponse)} className="space-y-4">
<div className="text-sm"> {content && content !== "" && (
Content: <p className="text-gray-500">{content}</p> <div className="text-sm">
</div> Content:
<RemirrorRichTextEditor
value={htmlContent ?? <p>{content}</p>}
customClassName="-mx-3 -my-3"
noBorder
borderOnFocus={false}
editable={false}
/>
</div>
)}
{response !== "" && ( {response !== "" && (
<div className="text-sm"> <div className="text-sm">
Response: <p className="text-gray-500">{response}</p> Response:
<RemirrorRichTextEditor
value={`<p>${response}</p>`}
customClassName="-mx-3 -my-3"
noBorder
borderOnFocus={false}
editable={false}
/>
</div> </div>
)} )}
{invalidResponse && ( {invalidResponse && (
@ -123,7 +137,11 @@ export const GptAssistantModal: React.FC<Props> = ({
type="text" type="text"
name="task" name="task"
register={register} register={register}
placeholder="Tell OpenAI what action to perform on this content..." placeholder={`${
content && content !== ""
? "Tell AI what action to perform on this content..."
: "Ask AI anything..."
}`}
autoComplete="off" autoComplete="off"
/> />
<div className={`flex gap-2 ${response === "" ? "justify-end" : "justify-between"}`}> <div className={`flex gap-2 ${response === "" ? "justify-end" : "justify-between"}`}>

View File

@ -239,7 +239,11 @@ export const IssueForm: FC<IssueFormProps> = ({
control={control} control={control}
render={({ field: { value } }) => ( render={({ field: { value } }) => (
<RemirrorRichTextEditor <RemirrorRichTextEditor
value={value} value={
!value || (typeof value === "object" && Object.keys(value).length === 0)
? watch("description_html")
: value
}
onJSONChange={(jsonValue) => setValue("description", jsonValue)} onJSONChange={(jsonValue) => setValue("description", jsonValue)}
onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)}
placeholder="Description" placeholder="Description"

View File

@ -45,12 +45,20 @@ export const CreateUpdatePageModal: React.FC<Props> = ({ isOpen, handleClose, da
mutate(RECENT_PAGES_LIST(projectId as string)); mutate(RECENT_PAGES_LIST(projectId as string));
mutate<IPage[]>( mutate<IPage[]>(
MY_PAGES_LIST(projectId as string), MY_PAGES_LIST(projectId as string),
(prevData) => [res, ...(prevData as IPage[])], (prevData) => {
if (!prevData) return undefined;
return [res, ...(prevData as IPage[])];
},
false false
); );
mutate<IPage[]>( mutate<IPage[]>(
ALL_PAGES_LIST(projectId as string), ALL_PAGES_LIST(projectId as string),
(prevData) => [res, ...(prevData as IPage[])], (prevData) => {
if (!prevData) return undefined;
return [res, ...(prevData as IPage[])];
},
false false
); );
onClose(); onClose();

View File

@ -2,6 +2,8 @@ import React, { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { mutate } from "swr";
// headless ui // headless ui
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
// services // services
@ -14,6 +16,13 @@ import { DangerButton, SecondaryButton } from "components/ui";
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// types // types
import type { IPage } from "types"; import type { IPage } from "types";
// fetch-keys
import {
ALL_PAGES_LIST,
FAVORITE_PAGES_LIST,
MY_PAGES_LIST,
RECENT_PAGES_LIST,
} from "constants/fetch-keys";
type TConfirmPageDeletionProps = { type TConfirmPageDeletionProps = {
isOpen: boolean; isOpen: boolean;
@ -45,6 +54,22 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = ({
await pagesService await pagesService
.deletePage(workspaceSlug as string, data.project, data.id) .deletePage(workspaceSlug as string, data.project, data.id)
.then(() => { .then(() => {
mutate(RECENT_PAGES_LIST(projectId as string));
mutate<IPage[]>(
MY_PAGES_LIST(projectId as string),
(prevData) => (prevData ?? []).filter((page) => page.id !== data?.id),
false
);
mutate<IPage[]>(
ALL_PAGES_LIST(projectId as string),
(prevData) => (prevData ?? []).filter((page) => page.id !== data?.id),
false
);
mutate<IPage[]>(
FAVORITE_PAGES_LIST(projectId as string),
(prevData) => (prevData ?? []).filter((page) => page.id !== data?.id),
false
);
handleClose(); handleClose();
setToastAlert({ setToastAlert({
type: "success", type: "success",

View File

@ -57,7 +57,6 @@ export const PagesView: React.FC<Props> = ({ pages, viewType }) => {
const handleAddToFavorites = (page: IPage) => { const handleAddToFavorites = (page: IPage) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
mutate(RECENT_PAGES_LIST(projectId as string));
mutate<IPage[]>( mutate<IPage[]>(
ALL_PAGES_LIST(projectId as string), ALL_PAGES_LIST(projectId as string),
(prevData) => (prevData) =>
@ -89,6 +88,7 @@ export const PagesView: React.FC<Props> = ({ pages, viewType }) => {
page: page.id, page: page.id,
}) })
.then(() => { .then(() => {
mutate(RECENT_PAGES_LIST(projectId as string));
setToastAlert({ setToastAlert({
type: "success", type: "success",
title: "Success!", title: "Success!",
@ -107,7 +107,6 @@ export const PagesView: React.FC<Props> = ({ pages, viewType }) => {
const handleRemoveFromFavorites = (page: IPage) => { const handleRemoveFromFavorites = (page: IPage) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
mutate(RECENT_PAGES_LIST(projectId as string));
mutate<IPage[]>( mutate<IPage[]>(
ALL_PAGES_LIST(projectId as string), ALL_PAGES_LIST(projectId as string),
(prevData) => (prevData) =>
@ -137,6 +136,7 @@ export const PagesView: React.FC<Props> = ({ pages, viewType }) => {
pagesService pagesService
.removePageFromFavorites(workspaceSlug as string, projectId as string, page.id) .removePageFromFavorites(workspaceSlug as string, projectId as string, page.id)
.then(() => { .then(() => {
mutate(RECENT_PAGES_LIST(projectId as string));
setToastAlert({ setToastAlert({
type: "success", type: "success",
title: "Success!", title: "Success!",

View File

@ -17,11 +17,16 @@ import useToast from "hooks/use-toast";
import { CreateUpdateIssueModal } from "components/issues"; import { CreateUpdateIssueModal } from "components/issues";
import { GptAssistantModal } from "components/core"; import { GptAssistantModal } from "components/core";
// ui // ui
import { CustomMenu, Loader, TextArea } from "components/ui"; import { CustomMenu, Input, Loader, TextArea } from "components/ui";
// icons // icons
import { LayerDiagonalIcon, WaterDropIcon } from "components/icons"; import { LayerDiagonalIcon } from "components/icons";
import { ArrowPathIcon } from "@heroicons/react/20/solid"; import { ArrowPathIcon } from "@heroicons/react/20/solid";
import { CheckIcon } from "@heroicons/react/24/outline"; import {
BoltIcon,
CheckIcon,
CursorArrowRaysIcon,
SparklesIcon,
} from "@heroicons/react/24/outline";
// helpers // helpers
import { copyTextToClipboard } from "helpers/string.helper"; import { copyTextToClipboard } from "helpers/string.helper";
// types // types
@ -163,21 +168,8 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails }) => {
const handleAiAssistance = async (response: string) => { const handleAiAssistance = async (response: string) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
setValue("description", { setValue("description", {});
type: "doc", setValue("description_html", `${watch("description_html")}<p>${response}</p>`);
content: [
{
type: "paragraph",
content: [
{
text: response,
type: "text",
},
],
},
],
});
setValue("description_html", `<p>${response}</p>`);
handleSubmit(updatePageBlock)() handleSubmit(updatePageBlock)()
.then(() => { .then(() => {
setToastAlert({ setToastAlert({
@ -253,7 +245,7 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails }) => {
}} }}
/> />
<div className="-mx-3 -mt-2 flex items-center justify-between gap-2"> <div className="-mx-3 -mt-2 flex items-center justify-between gap-2">
<TextArea <Input
id="name" id="name"
name="name" name="name"
placeholder="Block title" placeholder="Block title"
@ -261,11 +253,11 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails }) => {
onBlur={handleSubmit(updatePageBlock)} onBlur={handleSubmit(updatePageBlock)}
onChange={(e) => setValue("name", e.target.value)} onChange={(e) => setValue("name", e.target.value)}
required={true} required={true}
className="min-h-10 block w-full resize-none overflow-hidden border-none bg-transparent text-base font-medium" className="min-h-10 block w-full resize-none overflow-hidden border-none bg-transparent py-1 text-base font-medium ring-0 focus:ring-1 focus:ring-gray-200"
role="textbox" role="textbox"
/> />
<div className="flex flex-shrink-0 items-center gap-2"> <div className="flex flex-shrink-0 items-center gap-2">
{block.sync && ( {block.issue && block.sync && (
<div className="flex flex-shrink-0 cursor-default items-center gap-1 rounded bg-gray-100 py-1 px-1.5 text-xs"> <div className="flex flex-shrink-0 cursor-default items-center gap-1 rounded bg-gray-100 py-1 px-1.5 text-xs">
{isSyncing ? ( {isSyncing ? (
<ArrowPathIcon className="h-3 w-3 animate-spin" /> <ArrowPathIcon className="h-3 w-3 animate-spin" />
@ -285,12 +277,13 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails }) => {
)} )}
<button <button
type="button" type="button"
className="-mr-2 rounded px-1.5 py-1 text-xs hover:bg-gray-100" className="-mr-2 flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-gray-100"
onClick={() => setGptAssistantModal((prevData) => !prevData)} onClick={() => setGptAssistantModal((prevData) => !prevData)}
> >
<SparklesIcon className="h-4 w-4" />
AI AI
</button> </button>
<CustomMenu label={<WaterDropIcon width={14} height={15} />} noBorder noChevron> <CustomMenu label={<BoltIcon className="h-4.5 w-3.5" />} noBorder noChevron>
{block.issue ? ( {block.issue ? (
<> <>
<CustomMenu.MenuItem onClick={handleBlockSync}> <CustomMenu.MenuItem onClick={handleBlockSync}>
@ -312,7 +305,7 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails }) => {
</CustomMenu> </CustomMenu>
</div> </div>
</div> </div>
<div className="page-block-section relative -mx-3 -mt-5"> <div className="page-block-section font relative -mx-3 -mt-3">
<Controller <Controller
name="description" name="description"
control={control} control={control}
@ -327,8 +320,9 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails }) => {
onJSONChange={(jsonValue) => setValue("description", jsonValue)} onJSONChange={(jsonValue) => setValue("description", jsonValue)}
onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)}
placeholder="Block description..." placeholder="Block description..."
customClassName="text-gray-500" customClassName="border border-transparent"
noBorder noBorder
borderOnFocus
/> />
)} )}
/> />
@ -337,6 +331,7 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails }) => {
handleClose={() => setGptAssistantModal(false)} handleClose={() => setGptAssistantModal(false)}
inset="top-2 left-0" inset="top-2 left-0"
content={block.description_stripped} content={block.description_stripped}
htmlContent={block.description_html}
onResponse={handleAiAssistance} onResponse={handleAiAssistance}
/> />
</div> </div>

View File

@ -6,13 +6,12 @@ import { useRouter } from "next/router";
// ui // ui
import { CustomMenu, Tooltip } from "components/ui"; import { CustomMenu, Tooltip } from "components/ui";
// icons // icons
import { PencilIcon, StarIcon, TrashIcon } from "@heroicons/react/24/outline"; import { DocumentTextIcon, PencilIcon, StarIcon, TrashIcon } from "@heroicons/react/24/outline";
// helpers // helpers
import { truncateText } from "helpers/string.helper"; import { truncateText } from "helpers/string.helper";
import { renderShortDate, renderShortTime } from "helpers/date-time.helper"; import { renderShortDate, renderShortTime } from "helpers/date-time.helper";
// types // types
import { IPage } from "types"; import { IPage } from "types";
import { PencilScribbleIcon } from "components/icons";
type TSingleStatProps = { type TSingleStatProps = {
page: IPage; page: IPage;
@ -39,7 +38,7 @@ export const SinglePageListItem: React.FC<TSingleStatProps> = ({
<div className="relative rounded p-4 hover:bg-gray-100"> <div className="relative rounded p-4 hover:bg-gray-100">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<PencilScribbleIcon /> <DocumentTextIcon className="h-4 w-4" />
<p className="mr-2 truncate text-sm font-medium">{truncateText(page.name, 75)}</p> <p className="mr-2 truncate text-sm font-medium">{truncateText(page.name, 75)}</p>
{page.label_details.length > 0 && {page.label_details.length > 0 &&
page.label_details.map((label) => ( page.label_details.map((label) => (

View File

@ -6,7 +6,7 @@ import { Disclosure, Transition } from "@headlessui/react";
// ui // ui
import { CustomMenu } from "components/ui"; import { CustomMenu } from "components/ui";
// icons // icons
import { ChevronDownIcon } from "@heroicons/react/24/outline"; import { ChevronDownIcon, DocumentTextIcon } from "@heroicons/react/24/outline";
import { import {
ContrastIcon, ContrastIcon,
LayerDiagonalIcon, LayerDiagonalIcon,
@ -53,7 +53,7 @@ const navigation = (workspaceSlug: string, projectId: string) => [
{ {
name: "Pages", name: "Pages",
href: `/${workspaceSlug}/projects/${projectId}/pages`, href: `/${workspaceSlug}/projects/${projectId}/pages`,
icon: PencilScribbleIcon, icon: DocumentTextIcon,
}, },
{ {
name: "Settings", name: "Settings",

View File

@ -50,6 +50,7 @@ export interface IRemirrorRichTextEditor {
customClassName?: string; customClassName?: string;
gptOption?: boolean; gptOption?: boolean;
noBorder?: boolean; noBorder?: boolean;
borderOnFocus?: boolean;
} }
// eslint-disable-next-line no-duplicate-imports // eslint-disable-next-line no-duplicate-imports
@ -69,6 +70,7 @@ const RemirrorRichTextEditor: FC<IRemirrorRichTextEditor> = (props) => {
customClassName, customClassName,
gptOption = false, gptOption = false,
noBorder = false, noBorder = false,
borderOnFocus = true,
} = props; } = props;
const [imageLoader, setImageLoader] = useState(false); const [imageLoader, setImageLoader] = useState(false);
@ -188,9 +190,9 @@ const RemirrorRichTextEditor: FC<IRemirrorRichTextEditor> = (props) => {
manager={manager} manager={manager}
initialContent={state} initialContent={state}
classNames={[ classNames={[
`p-4 relative focus:outline-none rounded-md focus:border-theme ${ `p-4 relative focus:outline-none rounded-md focus:border-gray-200 ${
noBorder ? "" : "border" noBorder ? "" : "border"
} ${customClassName}`, } ${borderOnFocus ? "focus:border" : ""} ${customClassName}`,
]} ]}
editable={editable} editable={editable}
onBlur={() => { onBlur={() => {

View File

@ -8,14 +8,12 @@ import { Transition } from "@headlessui/react";
import useTheme from "hooks/use-theme"; import useTheme from "hooks/use-theme";
import useOutsideClickDetector from "hooks/use-outside-click-detector"; import useOutsideClickDetector from "hooks/use-outside-click-detector";
// icons // icons
import { ArrowLongLeftIcon, ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
import { import {
QuestionMarkCircleIcon, ArrowLongLeftIcon,
BoltIcon, ChatBubbleOvalLeftEllipsisIcon,
DocumentIcon, RocketLaunchIcon,
DiscordIcon, } from "@heroicons/react/24/outline";
GithubIcon, import { QuestionMarkCircleIcon, DocumentIcon, DiscordIcon, GithubIcon } from "components/icons";
} from "components/icons";
const helpOptions = [ const helpOptions = [
{ {
@ -77,7 +75,7 @@ export const WorkspaceHelpSection: FC<WorkspaceHelpSectionProps> = (props) => {
}} }}
title="Shortcuts" title="Shortcuts"
> >
<BoltIcon className="h-4 w-4 text-gray-500" /> <RocketLaunchIcon className="h-4 w-4 text-gray-500" />
{!sidebarCollapse && <span>Shortcuts</span>} {!sidebarCollapse && <span>Shortcuts</span>}
</button> </button>
<button <button

View File

@ -24,7 +24,7 @@ import AppLayout from "layouts/app-layout";
import { SinglePageBlock } from "components/pages"; import { SinglePageBlock } from "components/pages";
// ui // ui
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
import { CustomSearchSelect, Loader, PrimaryButton, TextArea } from "components/ui"; import { CustomSearchSelect, Loader, PrimaryButton, TextArea, Tooltip } from "components/ui";
// icons // icons
import { ArrowLeftIcon, PlusIcon, ShareIcon, StarIcon } from "@heroicons/react/24/outline"; import { ArrowLeftIcon, PlusIcon, ShareIcon, StarIcon } from "@heroicons/react/24/outline";
import { ColorPalletteIcon } from "components/icons"; import { ColorPalletteIcon } from "components/icons";
@ -324,9 +324,14 @@ const SinglePage: NextPage<UserAuth> = (props) => {
)} )}
</div> </div>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<span className="text-sm text-gray-500"> <Tooltip
{renderShortTime(pageDetails.created_at)} tooltipContent={`Page last updated at ${renderShortTime(pageDetails.updated_at)}`}
</span> theme="dark"
>
<span className="cursor-default text-sm text-gray-500">
{renderShortTime(pageDetails.updated_at)}
</span>
</Tooltip>
<PrimaryButton className="flex items-center gap-2" onClick={handleCopyText}> <PrimaryButton className="flex items-center gap-2" onClick={handleCopyText}>
<ShareIcon className="h-4 w-4" /> <ShareIcon className="h-4 w-4" />
Share Share
@ -393,7 +398,7 @@ const SinglePage: NextPage<UserAuth> = (props) => {
onBlur={handleSubmit(updatePage)} onBlur={handleSubmit(updatePage)}
onChange={(e) => setValue("name", e.target.value)} onChange={(e) => setValue("name", e.target.value)}
required={true} required={true}
className="min-h-10 block w-full resize-none overflow-hidden rounded border-none bg-transparent px-3 py-2 text-2xl font-semibold outline-none ring-0 focus:ring-1 focus:ring-theme" className="min-h-10 block w-full resize-none overflow-hidden rounded border-none bg-transparent px-3 py-2 text-2xl font-semibold outline-none ring-0 focus:ring-1 focus:ring-gray-200"
role="textbox" role="textbox"
/> />
</div> </div>
@ -413,7 +418,7 @@ const SinglePage: NextPage<UserAuth> = (props) => {
)} )}
<button <button
type="button" type="button"
className="flex items-center gap-1 rounded px-2.5 py-1 text-xs hover:bg-gray-100" className="flex items-center gap-1 rounded bg-gray-100 px-2.5 py-1 text-xs hover:bg-gray-200"
onClick={createPageBlock} onClick={createPageBlock}
disabled={isAddingBlock} disabled={isAddingBlock}
> >

View File

@ -120,12 +120,20 @@ const ProjectPages: NextPage<UserAuth> = (props) => {
mutate(RECENT_PAGES_LIST(projectId as string)); mutate(RECENT_PAGES_LIST(projectId as string));
mutate<IPage[]>( mutate<IPage[]>(
MY_PAGES_LIST(projectId as string), MY_PAGES_LIST(projectId as string),
(prevData) => [res, ...(prevData as IPage[])], (prevData) => {
if (!prevData) return undefined;
return [res, ...(prevData as IPage[])];
},
false false
); );
mutate<IPage[]>( mutate<IPage[]>(
ALL_PAGES_LIST(projectId as string), ALL_PAGES_LIST(projectId as string),
(prevData) => [res, ...(prevData as IPage[])], (prevData) => {
if (!prevData) return undefined;
return [res, ...(prevData as IPage[])];
},
false false
); );
}) })

View File

@ -1,5 +1,7 @@
// services // services
import APIService from "services/api.service"; import APIService from "services/api.service";
// types
import { IGptResponse } from "types";
const { NEXT_PUBLIC_API_BASE_URL } = process.env; const { NEXT_PUBLIC_API_BASE_URL } = process.env;
@ -12,7 +14,7 @@ class AiServices extends APIService {
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
data: { prompt: string; task: string } data: { prompt: string; task: string }
): Promise<any> { ): Promise<IGptResponse> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/ai-assistant/`, data) return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/ai-assistant/`, data)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {

4
apps/app/types/ai.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
export interface IGptResponse {
response: string;
response_html: string;
}

View File

@ -9,6 +9,7 @@ export * from "./modules";
export * from "./views"; export * from "./views";
export * from "./integration"; export * from "./integration";
export * from "./pages"; export * from "./pages";
export * from "./ai";
export type NestedKeyOf<ObjectType extends object> = { export type NestedKeyOf<ObjectType extends object> = {
[Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object [Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object