Skip to content

Commit

Permalink
feat: 정제 api 연동 (#86)
Browse files Browse the repository at this point in the history
* chore: sse.js 패키지 설치

* feat: gpt 정제 훅 구현

* chore: textarea-autosize 패키지 설치

* style: 저장 아이콘 추가

* refactor: 텍스트 prop 추가

* feat: 취합된 주관식 답변 컴포넌트 구현

* refactor: defaultValue 속성 제거

* refactor: 주관식 컴포넌트 연결
  • Loading branch information
hyoribogo authored Nov 29, 2023
1 parent 5e1fe01 commit 580652c
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 99 deletions.
112 changes: 63 additions & 49 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
"axios": "^1.5.1",
"chart.js": "^4.4.0",
"dayjs": "^1.11.10",
"framer-motion": "^10.16.4",
"react": "^18.2.0",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.11",
"react-hook-form": "^7.47.0",
"react-router-dom": "^6.17.0",
"react-textarea-autosize": "^8.5.3",
"rippleui": "^1.12.1",
"zod": "^3.22.4"
"sse": "github:mpetazzoni/sse.js"
},
"devDependencies": {
"@commitlint/cli": "^18.0.0",
Expand Down
1 change: 1 addition & 0 deletions src/assets/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ export { default as FilterReplyIcon } from './filter-reply.svg?react'
export { default as ImageIcon } from './image.svg?react'
export { default as SunIcon } from './sun.svg?react'
export { default as MoonIcon } from './moon.svg?react'
export { default as SaveIcon } from './save.svg?react'
3 changes: 3 additions & 0 deletions src/assets/icons/save.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { default as usePasswordCheck } from './usePasswordCheck'
export { default as useDarkMode } from './useDarkMode'
export { default as useToast } from './useToast'
export { default as useInfiniteScroll } from './useInfiniteScroll'
export { default as useRefine } from './useRefine'
116 changes: 116 additions & 0 deletions src/hooks/useRefine.ts
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
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
Loading

0 comments on commit 580652c

Please sign in to comment.