Skip to content

Commit

Permalink
feat(fe): integrate improve reply api (#89)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
cjeongmin authored Feb 13, 2025
1 parent cd9dcbc commit f904b8e
Show file tree
Hide file tree
Showing 10 changed files with 337 additions and 75 deletions.
2 changes: 1 addition & 1 deletion apps/client/src/features/ai-history/api/ai-history.ts
Original file line number Diff line number Diff line change
@@ -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']);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -54,7 +54,7 @@ export const useQuestionWritingSupport = ({
}
};

const requestEnable = !isQuestionImprovementInProgress && !isRetryQuestionImprovement;
const requestEnable = !isQuestionImprovementInProgress && !isRetryQuestionImprovementInProgress;

return {
questionImprovement,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function CreateQuestionModalSide({
}: Readonly<CreateQuestionModalSideProps>) {
return (
<div className='absolute right-8 flex h-[calc(100%-5rem)] w-12 flex-col items-center justify-between py-4'>
<Popover text={openPreview ? '마크다운 미리보기 끄기' : '마크다운 미리보기'} position='right'>
<Popover text={openPreview ? '마크다운 편집' : '마크다운 미리보기'} position='right'>
<button
className='flex h-10 w-10 items-center justify-center rounded-full border p-2 shadow-md'
onClick={() => setOpenPreview(!openPreview)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import axios from 'axios';
import { z } from 'zod';

export const RetryReplyImprovementRequestSchema = z.object({
token: z.string(),
sessionId: z.string(),
originalQuestion: z.string(),
original: z.string(),
received: z.string(),
retryMessage: z.string(),
});

export const RetryReplyImprovementResponseSchema = z.object({
result: z.object({
reply: z.string(),
}),
});

export type RetryReplyImprovementRequest = z.infer<typeof RetryReplyImprovementRequestSchema>;

export type RetryReplyImprovementResponse = z.infer<typeof RetryReplyImprovementResponseSchema>;

export const postRetryReplyImprovement = (body: RetryReplyImprovementRequest) =>
axios
.post<RetryReplyImprovementResponse>('/api/ai/reply-improve-retry', RetryReplyImprovementRequestSchema.parse(body))
.then((res) => RetryReplyImprovementResponseSchema.parse(res.data));
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import axios from 'axios';
import { z } from 'zod';

export const ReplyImprovementRequestSchema = z.object({
token: z.string(),
sessionId: z.string(),
body: z.string(),
originalQuestion: z.string(),
});

export const ReplyImprovementResponseSchema = z.object({
result: z.object({
reply: z.string(),
}),
});

export type ReplyImprovementRequest = z.infer<typeof ReplyImprovementRequestSchema>;

export type ReplyImprovementResponse = z.infer<typeof ReplyImprovementResponseSchema>;

export const postReplyImprovement = (body: ReplyImprovementRequest) =>
axios
.post<ReplyImprovementResponse>('/api/ai/reply-improve', ReplyImprovementRequestSchema.parse(body))
.then((res) => ReplyImprovementResponseSchema.parse(res.data));
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useMutation } from '@tanstack/react-query';
import { useState } from 'react';

import { AIRequestType, postAIHistory } from '@/features/ai-history';
import { postRetryReplyImprovement } from '@/features/create-update-reply/api/improve-reply-retry.api';
import { postReplyImprovement } from '@/features/create-update-reply/api/improve-reply.api';

export const useReplyWritingSupport = ({
questionBody,
body,
handleAccept,
}: {
questionBody: string;
body: string;
handleAccept: (body: string) => void;
}) => {
const [supportResult, setSupportResult] = useState<string | null>(null);
const [supportType, setSupportType] = useState<AIRequestType | null>(null);

const { mutate: replyImprovement, isPending: isReplyImprovementInProgress } = useMutation({
mutationFn: postReplyImprovement,
onSuccess: (data) => {
setSupportResult(data.result.reply);
},
});

const { mutate: retryReplyImprovement, isPending: isRetryReplyImprovementInProgress } = useMutation({
mutationFn: postRetryReplyImprovement,
onSuccess: (data) => {
setSupportResult(data.result.reply);
},
});

const request = `# Q)\n${questionBody}\n\n# A)\n${body}`;

const accept = () => {
if (supportResult && supportType) {
handleAccept(supportResult);
postAIHistory({
promptName: supportType,
request,
response: supportResult,
result: 'ACCEPT',
});
setSupportResult(null);
}
};

const reject = () => {
if (supportResult && supportType) {
postAIHistory({
promptName: supportType,
request,
response: supportResult,
result: 'REJECT',
});
setSupportResult(null);
}
};

const requestEnable = !isReplyImprovementInProgress && !isRetryReplyImprovementInProgress;

return {
replyImprovement,
retryReplyImprovement,
supportResult,
setSupportResult,
supportType,
setSupportType,
accept,
reject,
requestEnable,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { useState } from 'react';

import { ContentType } from '@/features/create-update-reply/model/reply-modal.type';
import { useReplyMutation } from '@/features/create-update-reply/model/useReplyMutation';
import { useReplyWritingSupport } from '@/features/create-update-reply/model/useReplyWritingSupport';
import CreateReplyModalFooter from '@/features/create-update-reply/ui/CreateReplyModalFooter';
import CreateReplyModalSide from '@/features/create-update-reply/ui/CreateReplyModalSide';
import ReplyContentView from '@/features/create-update-reply/ui/ReplyContentView';

import { Question, Reply } from '@/entities/session';
import { Question, Reply, useSessionStore } from '@/entities/session';
import { getContentBodyLength, isValidBodyLength } from '@/entities/session/model/qna.util';

interface CreateReplyModalProps {
Expand All @@ -15,13 +16,51 @@ interface CreateReplyModalProps {
}

function CreateReplyModal({ question, reply }: Readonly<CreateReplyModalProps>) {
const token = useSessionStore((state) => state.sessionToken);
const sessionId = useSessionStore((state) => state.sessionId);

const { body, setBody, handleSubmit, submitDisabled } = useReplyMutation(question, reply);
const {
supportType,
supportResult,
requestEnable,
setSupportType,
replyImprovement,
retryReplyImprovement,
accept,
reject,
} = useReplyWritingSupport({ questionBody: question?.body ?? '', body, handleAccept: setBody });

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

const bodyLength = getContentBodyLength(body);
const bodyLength = getContentBodyLength(supportResult ?? body);
const isValidLength = isValidBodyLength(bodyLength);

const buttonEnabled = !submitDisabled && isValidLength && contentType !== 'question';

const handleCreateOrUpdate = () => {
if (buttonEnabled && isValidLength) handleSubmit();
};

const handleReplyImprovement = () => {
if (buttonEnabled && isValidLength && question && sessionId && token) {
setSupportType('IMPROVE_REPLY');
replyImprovement({ token, sessionId, body, originalQuestion: question.body });
}
};

const buttonEnabled = !submitDisabled && isValidBodyLength(bodyLength) && contentType !== 'question';
const handleRetry = (requirements: string) => {
if (sessionId && token && question && supportResult && supportType) {
retryReplyImprovement({
token,
sessionId,
originalQuestion: question.body,
original: body,
received: supportResult,
retryMessage: requirements,
});
}
};

return (
<div className='relative flex h-[20rem] w-[40rem] flex-col rounded-lg bg-gray-50 p-4'>
Expand All @@ -30,11 +69,23 @@ function CreateReplyModal({ question, reply }: Readonly<CreateReplyModalProps>)
contentType={contentType}
questionBody={question?.body ?? '질문을 찾을 수 없습니다.'}
replyBody={body}
onChange={setBody}
onReplyBodyChange={setBody}
supportResult={supportResult}
isWritingPending={!requestEnable}
/>
<CreateReplyModalSide bodyLength={bodyLength} contentType={contentType} setContentType={setContentType} />
</div>
<CreateReplyModalFooter reply={reply} buttonEnabled={buttonEnabled} handleSubmit={handleSubmit} />
<CreateReplyModalFooter
question={question}
reply={reply}
buttonEnabled={buttonEnabled}
supportResult={supportResult}
handleCreateOrUpdate={handleCreateOrUpdate}
handleReplyImprovement={handleReplyImprovement}
handleRetry={handleRetry}
accept={accept}
reject={reject}
/>
</div>
);
}
Expand Down
Loading

0 comments on commit f904b8e

Please sign in to comment.