-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* chore: sse.js 패키지 설치 * feat: gpt 정제 훅 구현 * chore: textarea-autosize 패키지 설치 * style: 저장 아이콘 추가 * refactor: 텍스트 prop 추가 * feat: 취합된 주관식 답변 컴포넌트 구현 * refactor: defaultValue 속성 제거 * refactor: 주관식 컴포넌트 연결
- Loading branch information
Showing
8 changed files
with
250 additions
and
99 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import { ChangeEvent, useEffect, useRef, useState } from 'react' | ||
import { SSE } from 'sse' | ||
|
||
const useRefine = ({ text = '' }: { text: string }) => { | ||
const [prompt, setPrompt] = useState(text) | ||
const [isLoading, setIsLoading] = useState(false) | ||
const [result, setResult] = useState('') | ||
|
||
const resultRef = useRef<string>() | ||
|
||
useEffect(() => { | ||
resultRef.current = result | ||
}, [result]) | ||
|
||
const clearState = () => { | ||
setPrompt('') | ||
setResult('') | ||
} | ||
|
||
const showAlertForEmptyPrompt = () => { | ||
if (prompt === '') { | ||
alert('빈 텍스트는 정제할 수 없습니다!') | ||
|
||
return true | ||
} | ||
|
||
return false | ||
} | ||
|
||
const refineConfig = { | ||
model: 'gpt-3.5-turbo', | ||
messages: [ | ||
{ content: prompt, role: 'user' }, | ||
{ | ||
content: `이 글들은 동료에 대한 평가 글이야. "," 로 구분된 구문들에 대해 비판이 아닌 욕설, 비난, 부정적 감정 표현을 제거한 뒤 핵심만 간략하게 요약해서 최대한 짧은 하나의 구문을 만들어줘. 또한 조건이 있어. 모든 구문을 존댓말로 통일해야 해. 오로지 전달 받은 텍스트로만 정제해야 해. 너가 새로운 문구를 지어내면 안돼.`, | ||
role: 'system', | ||
}, | ||
], | ||
temperature: 0.7, | ||
max_tokens: 1300, | ||
stream: true, | ||
n: 1, | ||
} | ||
|
||
const createSSEConfig = () => { | ||
return { | ||
headers: { | ||
'Content-Type': 'application/json; charset=utf-8', | ||
Authorization: `Bearer ${import.meta.env.VITE_OPEN_API_KEY}`, | ||
}, | ||
method: 'POST', | ||
payload: JSON.stringify(refineConfig), | ||
} | ||
} | ||
|
||
const handleClear = () => { | ||
clearState() | ||
} | ||
|
||
const handleRefine = async () => { | ||
if (showAlertForEmptyPrompt() || isLoading) { | ||
return | ||
} | ||
|
||
if (result) { | ||
setPrompt(result) | ||
} | ||
|
||
setIsLoading(true) | ||
setResult('') | ||
|
||
const url = 'https://api.openai.com/v1/chat/completions' | ||
const source = new SSE(url, createSSEConfig()) | ||
|
||
source.addEventListener('message', (e: MessageEvent) => { | ||
if (e.data === '[DONE]') { | ||
source.close() | ||
setIsLoading(false) | ||
|
||
return | ||
} | ||
|
||
const payload = JSON.parse(e.data) | ||
const text = payload.choices[0].delta.content | ||
|
||
if (text === '\n' || text === undefined) { | ||
return | ||
} | ||
|
||
resultRef.current += text | ||
setResult(resultRef.current!) | ||
}) | ||
|
||
source.addEventListener('readystatechange', () => { | ||
if (source.readyState === 4) { | ||
setIsLoading(false) | ||
} | ||
}) | ||
|
||
source.stream() | ||
} | ||
|
||
const handleChangePrompt = (e: ChangeEvent<HTMLTextAreaElement>) => { | ||
setPrompt(e.target.value) | ||
} | ||
|
||
const handlers = { | ||
handleClear, | ||
handleRefine, | ||
handleChangePrompt, | ||
} | ||
|
||
return { prompt, result, isLoading, handlers } | ||
} | ||
|
||
export default useRefine |
55 changes: 55 additions & 0 deletions
55
src/pages/CreatedReviewManagePage/components/AnswerGroup/RenderRefinedSubjective.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import ReactTextareaAutosize from 'react-textarea-autosize' | ||
import { useRefine } from '@/hooks' | ||
import { IconButton } from '@/components' | ||
import { FilterReplyIcon, SaveIcon } from '@/assets/icons' | ||
|
||
interface RenderRefinedSubjectiveProps { | ||
text: string | ||
} | ||
|
||
const RenderRefinedSubjective = ({ text }: RenderRefinedSubjectiveProps) => { | ||
const { handlers, isLoading, prompt, result } = useRefine({ text }) | ||
const { handleChangePrompt, handleRefine } = handlers | ||
|
||
return ( | ||
<div className="relative flex w-full flex-col bg-transparent p-2.5"> | ||
{isLoading && ( | ||
<div className="spinner-dot-pulse absolute right-6 top-6 [--spinner-color:var(--blue-9)]"> | ||
<div className="spinner-pulse-dot"></div> | ||
</div> | ||
)} | ||
|
||
<ReactTextareaAutosize | ||
onChange={handleChangePrompt} | ||
className="m-0 w-full shrink-0 resize-none rounded-none border border-gray-200 bg-transparent p-5 text-sm dark:text-white" | ||
value={result ? result : prompt} | ||
></ReactTextareaAutosize> | ||
|
||
<div className="ml-2.5 flex gap-2 p-2.5"> | ||
<IconButton | ||
className="mt-2.5 h-7 gap-1 rounded-md border border-gray-200 bg-gray-400 text-sm text-black" | ||
text="정제" | ||
onClick={handleRefine} | ||
> | ||
<FilterReplyIcon className="h-4 w-4" /> | ||
</IconButton> | ||
|
||
<IconButton | ||
disabled | ||
className="mt-2.5 h-7 gap-1 rounded-md border border-gray-200 bg-gray-400 text-sm text-black" | ||
text="저장" | ||
onClick={() => { | ||
if (isLoading) { | ||
return | ||
} | ||
//TODO: 저장 API 호출 | ||
}} | ||
> | ||
<SaveIcon /> | ||
</IconButton> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
export default RenderRefinedSubjective |
Oops, something went wrong.