Skip to content

Commit f904b8e

Browse files
authored
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
1 parent cd9dcbc commit f904b8e

File tree

10 files changed

+337
-75
lines changed

10 files changed

+337
-75
lines changed

apps/client/src/features/ai-history/api/ai-history.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import axios from 'axios';
22
import { z } from 'zod';
33

4-
export const AIRequestTypeSchema = z.enum(['IMPROVE_QUESTION']);
4+
export const AIRequestTypeSchema = z.enum(['IMPROVE_QUESTION', 'IMPROVE_REPLY']);
55

66
export const AIResultTypeSchema = z.enum(['ACCEPT', 'REJECT']);
77

apps/client/src/features/create-update-question/model/useQuestionWritingSupport.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const useQuestionWritingSupport = ({
2222
},
2323
});
2424

25-
const { mutate: retryQuestionImprovement, isPending: isRetryQuestionImprovement } = useMutation({
25+
const { mutate: retryQuestionImprovement, isPending: isRetryQuestionImprovementInProgress } = useMutation({
2626
mutationFn: postRetryQuestionImprovement,
2727
onSuccess: (data) => {
2828
setSupportResult(data.result.question);
@@ -54,7 +54,7 @@ export const useQuestionWritingSupport = ({
5454
}
5555
};
5656

57-
const requestEnable = !isQuestionImprovementInProgress && !isRetryQuestionImprovement;
57+
const requestEnable = !isQuestionImprovementInProgress && !isRetryQuestionImprovementInProgress;
5858

5959
return {
6060
questionImprovement,

apps/client/src/features/create-update-question/ui/CreateQuestionModalSide.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default function CreateQuestionModalSide({
2424
}: Readonly<CreateQuestionModalSideProps>) {
2525
return (
2626
<div className='absolute right-8 flex h-[calc(100%-5rem)] w-12 flex-col items-center justify-between py-4'>
27-
<Popover text={openPreview ? '마크다운 미리보기 끄기' : '마크다운 미리보기'} position='right'>
27+
<Popover text={openPreview ? '마크다운 편집' : '마크다운 미리보기'} position='right'>
2828
<button
2929
className='flex h-10 w-10 items-center justify-center rounded-full border p-2 shadow-md'
3030
onClick={() => setOpenPreview(!openPreview)}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import axios from 'axios';
2+
import { z } from 'zod';
3+
4+
export const RetryReplyImprovementRequestSchema = z.object({
5+
token: z.string(),
6+
sessionId: z.string(),
7+
originalQuestion: z.string(),
8+
original: z.string(),
9+
received: z.string(),
10+
retryMessage: z.string(),
11+
});
12+
13+
export const RetryReplyImprovementResponseSchema = z.object({
14+
result: z.object({
15+
reply: z.string(),
16+
}),
17+
});
18+
19+
export type RetryReplyImprovementRequest = z.infer<typeof RetryReplyImprovementRequestSchema>;
20+
21+
export type RetryReplyImprovementResponse = z.infer<typeof RetryReplyImprovementResponseSchema>;
22+
23+
export const postRetryReplyImprovement = (body: RetryReplyImprovementRequest) =>
24+
axios
25+
.post<RetryReplyImprovementResponse>('/api/ai/reply-improve-retry', RetryReplyImprovementRequestSchema.parse(body))
26+
.then((res) => RetryReplyImprovementResponseSchema.parse(res.data));
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import axios from 'axios';
2+
import { z } from 'zod';
3+
4+
export const ReplyImprovementRequestSchema = z.object({
5+
token: z.string(),
6+
sessionId: z.string(),
7+
body: z.string(),
8+
originalQuestion: z.string(),
9+
});
10+
11+
export const ReplyImprovementResponseSchema = z.object({
12+
result: z.object({
13+
reply: z.string(),
14+
}),
15+
});
16+
17+
export type ReplyImprovementRequest = z.infer<typeof ReplyImprovementRequestSchema>;
18+
19+
export type ReplyImprovementResponse = z.infer<typeof ReplyImprovementResponseSchema>;
20+
21+
export const postReplyImprovement = (body: ReplyImprovementRequest) =>
22+
axios
23+
.post<ReplyImprovementResponse>('/api/ai/reply-improve', ReplyImprovementRequestSchema.parse(body))
24+
.then((res) => ReplyImprovementResponseSchema.parse(res.data));
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { useMutation } from '@tanstack/react-query';
2+
import { useState } from 'react';
3+
4+
import { AIRequestType, postAIHistory } from '@/features/ai-history';
5+
import { postRetryReplyImprovement } from '@/features/create-update-reply/api/improve-reply-retry.api';
6+
import { postReplyImprovement } from '@/features/create-update-reply/api/improve-reply.api';
7+
8+
export const useReplyWritingSupport = ({
9+
questionBody,
10+
body,
11+
handleAccept,
12+
}: {
13+
questionBody: string;
14+
body: string;
15+
handleAccept: (body: string) => void;
16+
}) => {
17+
const [supportResult, setSupportResult] = useState<string | null>(null);
18+
const [supportType, setSupportType] = useState<AIRequestType | null>(null);
19+
20+
const { mutate: replyImprovement, isPending: isReplyImprovementInProgress } = useMutation({
21+
mutationFn: postReplyImprovement,
22+
onSuccess: (data) => {
23+
setSupportResult(data.result.reply);
24+
},
25+
});
26+
27+
const { mutate: retryReplyImprovement, isPending: isRetryReplyImprovementInProgress } = useMutation({
28+
mutationFn: postRetryReplyImprovement,
29+
onSuccess: (data) => {
30+
setSupportResult(data.result.reply);
31+
},
32+
});
33+
34+
const request = `# Q)\n${questionBody}\n\n# A)\n${body}`;
35+
36+
const accept = () => {
37+
if (supportResult && supportType) {
38+
handleAccept(supportResult);
39+
postAIHistory({
40+
promptName: supportType,
41+
request,
42+
response: supportResult,
43+
result: 'ACCEPT',
44+
});
45+
setSupportResult(null);
46+
}
47+
};
48+
49+
const reject = () => {
50+
if (supportResult && supportType) {
51+
postAIHistory({
52+
promptName: supportType,
53+
request,
54+
response: supportResult,
55+
result: 'REJECT',
56+
});
57+
setSupportResult(null);
58+
}
59+
};
60+
61+
const requestEnable = !isReplyImprovementInProgress && !isRetryReplyImprovementInProgress;
62+
63+
return {
64+
replyImprovement,
65+
retryReplyImprovement,
66+
supportResult,
67+
setSupportResult,
68+
supportType,
69+
setSupportType,
70+
accept,
71+
reject,
72+
requestEnable,
73+
};
74+
};

apps/client/src/features/create-update-reply/ui/CreateReplyModal.tsx

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import { useState } from 'react';
22

33
import { ContentType } from '@/features/create-update-reply/model/reply-modal.type';
44
import { useReplyMutation } from '@/features/create-update-reply/model/useReplyMutation';
5+
import { useReplyWritingSupport } from '@/features/create-update-reply/model/useReplyWritingSupport';
56
import CreateReplyModalFooter from '@/features/create-update-reply/ui/CreateReplyModalFooter';
67
import CreateReplyModalSide from '@/features/create-update-reply/ui/CreateReplyModalSide';
78
import ReplyContentView from '@/features/create-update-reply/ui/ReplyContentView';
89

9-
import { Question, Reply } from '@/entities/session';
10+
import { Question, Reply, useSessionStore } from '@/entities/session';
1011
import { getContentBodyLength, isValidBodyLength } from '@/entities/session/model/qna.util';
1112

1213
interface CreateReplyModalProps {
@@ -15,13 +16,51 @@ interface CreateReplyModalProps {
1516
}
1617

1718
function CreateReplyModal({ question, reply }: Readonly<CreateReplyModalProps>) {
19+
const token = useSessionStore((state) => state.sessionToken);
20+
const sessionId = useSessionStore((state) => state.sessionId);
21+
1822
const { body, setBody, handleSubmit, submitDisabled } = useReplyMutation(question, reply);
23+
const {
24+
supportType,
25+
supportResult,
26+
requestEnable,
27+
setSupportType,
28+
replyImprovement,
29+
retryReplyImprovement,
30+
accept,
31+
reject,
32+
} = useReplyWritingSupport({ questionBody: question?.body ?? '', body, handleAccept: setBody });
1933

2034
const [contentType, setContentType] = useState<ContentType>('reply');
2135

22-
const bodyLength = getContentBodyLength(body);
36+
const bodyLength = getContentBodyLength(supportResult ?? body);
37+
const isValidLength = isValidBodyLength(bodyLength);
38+
39+
const buttonEnabled = !submitDisabled && isValidLength && contentType !== 'question';
40+
41+
const handleCreateOrUpdate = () => {
42+
if (buttonEnabled && isValidLength) handleSubmit();
43+
};
44+
45+
const handleReplyImprovement = () => {
46+
if (buttonEnabled && isValidLength && question && sessionId && token) {
47+
setSupportType('IMPROVE_REPLY');
48+
replyImprovement({ token, sessionId, body, originalQuestion: question.body });
49+
}
50+
};
2351

24-
const buttonEnabled = !submitDisabled && isValidBodyLength(bodyLength) && contentType !== 'question';
52+
const handleRetry = (requirements: string) => {
53+
if (sessionId && token && question && supportResult && supportType) {
54+
retryReplyImprovement({
55+
token,
56+
sessionId,
57+
originalQuestion: question.body,
58+
original: body,
59+
received: supportResult,
60+
retryMessage: requirements,
61+
});
62+
}
63+
};
2564

2665
return (
2766
<div className='relative flex h-[20rem] w-[40rem] flex-col rounded-lg bg-gray-50 p-4'>
@@ -30,11 +69,23 @@ function CreateReplyModal({ question, reply }: Readonly<CreateReplyModalProps>)
3069
contentType={contentType}
3170
questionBody={question?.body ?? '질문을 찾을 수 없습니다.'}
3271
replyBody={body}
33-
onChange={setBody}
72+
onReplyBodyChange={setBody}
73+
supportResult={supportResult}
74+
isWritingPending={!requestEnable}
3475
/>
3576
<CreateReplyModalSide bodyLength={bodyLength} contentType={contentType} setContentType={setContentType} />
3677
</div>
37-
<CreateReplyModalFooter reply={reply} buttonEnabled={buttonEnabled} handleSubmit={handleSubmit} />
78+
<CreateReplyModalFooter
79+
question={question}
80+
reply={reply}
81+
buttonEnabled={buttonEnabled}
82+
supportResult={supportResult}
83+
handleCreateOrUpdate={handleCreateOrUpdate}
84+
handleReplyImprovement={handleReplyImprovement}
85+
handleRetry={handleRetry}
86+
accept={accept}
87+
reject={reject}
88+
/>
3889
</div>
3990
);
4091
}

0 commit comments

Comments
 (0)