From f904b8e2b50309e23a6f3628a9a1b28c4a94e0a0 Mon Sep 17 00:00:00 2001 From: Choi Jeongmin Date: Thu, 13 Feb 2025 17:57:39 +0900 Subject: [PATCH] feat(fe): integrate improve reply api (#89) * feat: add reply improvement and retry APIs * feat: implement reply writing support with improvement and retry functionality * feat: update Popover component text for markdown preview and content type switching --- .../src/features/ai-history/api/ai-history.ts | 2 +- .../model/useQuestionWritingSupport.ts | 4 +- .../ui/CreateQuestionModalSide.tsx | 2 +- .../api/improve-reply-retry.api.ts | 26 +++ .../api/improve-reply.api.ts | 24 +++ .../model/useReplyWritingSupport.ts | 74 ++++++++ .../ui/CreateReplyModal.tsx | 61 ++++++- .../ui/CreateReplyModalFooter.tsx | 164 ++++++++++++------ .../ui/CreateReplyModalSide.tsx | 30 ++-- .../ui/ReplyContentView.tsx | 25 ++- 10 files changed, 337 insertions(+), 75 deletions(-) create mode 100644 apps/client/src/features/create-update-reply/api/improve-reply-retry.api.ts create mode 100644 apps/client/src/features/create-update-reply/api/improve-reply.api.ts create mode 100644 apps/client/src/features/create-update-reply/model/useReplyWritingSupport.ts diff --git a/apps/client/src/features/ai-history/api/ai-history.ts b/apps/client/src/features/ai-history/api/ai-history.ts index 8decfb3..2a38989 100644 --- a/apps/client/src/features/ai-history/api/ai-history.ts +++ b/apps/client/src/features/ai-history/api/ai-history.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { z } from 'zod'; -export const AIRequestTypeSchema = z.enum(['IMPROVE_QUESTION']); +export const AIRequestTypeSchema = z.enum(['IMPROVE_QUESTION', 'IMPROVE_REPLY']); export const AIResultTypeSchema = z.enum(['ACCEPT', 'REJECT']); diff --git a/apps/client/src/features/create-update-question/model/useQuestionWritingSupport.ts b/apps/client/src/features/create-update-question/model/useQuestionWritingSupport.ts index af82528..16d1256 100644 --- a/apps/client/src/features/create-update-question/model/useQuestionWritingSupport.ts +++ b/apps/client/src/features/create-update-question/model/useQuestionWritingSupport.ts @@ -22,7 +22,7 @@ export const useQuestionWritingSupport = ({ }, }); - const { mutate: retryQuestionImprovement, isPending: isRetryQuestionImprovement } = useMutation({ + const { mutate: retryQuestionImprovement, isPending: isRetryQuestionImprovementInProgress } = useMutation({ mutationFn: postRetryQuestionImprovement, onSuccess: (data) => { setSupportResult(data.result.question); @@ -54,7 +54,7 @@ export const useQuestionWritingSupport = ({ } }; - const requestEnable = !isQuestionImprovementInProgress && !isRetryQuestionImprovement; + const requestEnable = !isQuestionImprovementInProgress && !isRetryQuestionImprovementInProgress; return { questionImprovement, diff --git a/apps/client/src/features/create-update-question/ui/CreateQuestionModalSide.tsx b/apps/client/src/features/create-update-question/ui/CreateQuestionModalSide.tsx index b024fa7..43ee2f8 100644 --- a/apps/client/src/features/create-update-question/ui/CreateQuestionModalSide.tsx +++ b/apps/client/src/features/create-update-question/ui/CreateQuestionModalSide.tsx @@ -24,7 +24,7 @@ export default function CreateQuestionModalSide({ }: Readonly) { return (
- +
); } diff --git a/apps/client/src/features/create-update-reply/ui/CreateReplyModalFooter.tsx b/apps/client/src/features/create-update-reply/ui/CreateReplyModalFooter.tsx index 300e0ed..088a722 100644 --- a/apps/client/src/features/create-update-reply/ui/CreateReplyModalFooter.tsx +++ b/apps/client/src/features/create-update-reply/ui/CreateReplyModalFooter.tsx @@ -1,69 +1,135 @@ -import { Reply } from '@/entities/session'; +import { useState } from 'react'; + +import { Question, Reply } from '@/entities/session'; import { Button } from '@/shared/ui/button'; import { useModalContext } from '@/shared/ui/modal'; -import { useToastStore } from '@/shared/ui/toast'; +import { Popover } from '@/shared/ui/popover'; interface CreateReplyModalFooterProps { + supportResult: string | null; + question?: Question; reply?: Reply; buttonEnabled: boolean; - handleSubmit: () => void; + handleReplyImprovement: () => void; + handleRetry: (requirements: string) => void; + handleCreateOrUpdate: () => void; + accept: () => void; + reject: () => void; +} + +function RetryActions({ + handleRetry, + reject, + accept, +}: Readonly<{ + handleRetry: (requirements: string) => void; + reject: () => void; + accept: () => void; +}>) { + const [retryEnabled, setRetryEnabled] = useState(false); + const [retryRequirements, setRetryRequirements] = useState(''); + const [isRetryRequirementsTooLong, setIsRetryRequirementsTooLong] = useState(false); + + const handleChangeRetryRequirements = (e: React.ChangeEvent) => { + const value = e.target.value; + if (value.length <= 150) { + setRetryRequirements(value); + setIsRetryRequirementsTooLong(false); + } else { + setIsRetryRequirementsTooLong(true); + } + }; + + const handleCancelRetry = () => { + setRetryEnabled(false); + setRetryRequirements(''); + }; + + const handleRetryRequest = () => { + handleRetry(retryRequirements); + setRetryEnabled(false); + setRetryRequirements(''); + }; + + if (retryEnabled) { + return ( +
+ + + + + +
+ ); + } + + return ( +
+ + + +
+ ); } export default function CreateReplyModalFooter({ + supportResult, reply, buttonEnabled, - handleSubmit, + handleReplyImprovement, + handleRetry, + handleCreateOrUpdate, + accept, + reject, }: Readonly) { const { closeModal } = useModalContext(); - const addToast = useToastStore((state) => state.addToast); - return (
-
- - -
-
- - -
+ {supportResult === null ? ( + <> + +
+ + +
+ + ) : ( + + )}
); } diff --git a/apps/client/src/features/create-update-reply/ui/CreateReplyModalSide.tsx b/apps/client/src/features/create-update-reply/ui/CreateReplyModalSide.tsx index 3fb1956..d3f7cc2 100644 --- a/apps/client/src/features/create-update-reply/ui/CreateReplyModalSide.tsx +++ b/apps/client/src/features/create-update-reply/ui/CreateReplyModalSide.tsx @@ -4,6 +4,8 @@ import { VscEdit, VscMarkdown } from 'react-icons/vsc'; import { ContentType } from '@/features/create-update-reply/model/reply-modal.type'; +import { Popover } from '@/shared/ui/popover'; + interface CreateReplyModalSideProps { bodyLength: number; contentType: ContentType; @@ -28,20 +30,24 @@ export default function CreateReplyModalSide({
{(contentType === 'reply' || contentType === 'question') && ( - + + + )} {(contentType === 'reply' || contentType === 'preview') && ( - + + + )}
500 ? 'text-red-600' : 'text-slate-400'} ${textSize(bodyLength)}`}> diff --git a/apps/client/src/features/create-update-reply/ui/ReplyContentView.tsx b/apps/client/src/features/create-update-reply/ui/ReplyContentView.tsx index d4db024..d5657c4 100644 --- a/apps/client/src/features/create-update-reply/ui/ReplyContentView.tsx +++ b/apps/client/src/features/create-update-reply/ui/ReplyContentView.tsx @@ -5,22 +5,36 @@ import { ContentType } from '@/features/create-update-reply/model/reply-modal.ty import { Question, Reply } from '@/entities/session'; interface ReplyContentViewProps { + supportResult: string | null; contentType: ContentType; questionBody: Question['body']; replyBody: Reply['body']; - onChange: (body: string) => void; + isWritingPending: boolean; + onReplyBodyChange: (body: string) => void; } export default function ReplyContentView({ + supportResult, contentType, questionBody, replyBody, - onChange, + isWritingPending, + onReplyBodyChange, }: Readonly) { + if (isWritingPending) { + return ( +
+
+
+ ); + } + if (contentType === 'preview') { return (
- {replyBody} + + {supportResult === null ? replyBody : supportResult} +
); } @@ -38,8 +52,9 @@ export default function ReplyContentView({ return (