Skip to content

Commit

Permalink
[feat] AI 필터를 껐다 켰다 하는 기능 구현 (#194)
Browse files Browse the repository at this point in the history
* style : 채팅 스타일 수정

* feat : 방송 송출 링크 추가

* fix : 닉네임을 변경해도 채팅 닉네임은 이전 닉네임으로 나가는 오류 수정

* fix : 채팅창 가로 스크롤 제거

* feat : AI 필터링을 껐다 켰다 하는 기능 구현

* fix : 맥에서 엔터를 눌렀을 때 채팅이 두 번 가는 오류 수정

* fix : 리뷰 내용 반영

* fix : 리뷰 내용 반영

* fix : 리뷰 내용 반영

* fix : 리뷰 내용 반영
  • Loading branch information
Novrule authored Dec 8, 2023
1 parent 32c4055 commit 33d6488
Show file tree
Hide file tree
Showing 11 changed files with 95 additions and 46 deletions.
4 changes: 2 additions & 2 deletions client/src/components/Access/Access.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import * as styles from './Access.styles'
interface AccessProps {
leftButton: string
rightButton: string
onLeftButton: () => void
onLeftButton: (changeUser: boolean) => void
onRightButton: () => void
}

const Access = ({ leftButton, rightButton, onLeftButton, onRightButton }: AccessProps) => {
return (
<styles.Access>
<styles.Button onClick={onLeftButton}>{leftButton}</styles.Button>
<styles.Button onClick={() => onLeftButton(false)}>{leftButton}</styles.Button>
<styles.Button onClick={onRightButton}>{rightButton}</styles.Button>
</styles.Access>
)
Expand Down
11 changes: 7 additions & 4 deletions client/src/components/Chatting/Chatting.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@ export const Chatting = styled.div`

export const Nickname = styled.div`
${TYPO.BOLD_R}
max-width: 5rem;
overflow: hidden;
max-width: 3rem;
line-height: 2rem;
text-align: left;
white-space: nowrap;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`

export const Message = styled.div`
${TYPO.LIGHT_R}
word-break: break-all;
max-width: 19.375rem;
line-height: 2rem;
text-align: left;
word-break: break-all;
`
10 changes: 5 additions & 5 deletions client/src/components/Modal/SettingModal/SettingModal.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ export const SettingContainer = styled.div`
height: 2.25rem;
`

export const ToggleContainer = styled.div<{ isDarkMode: boolean }>`
export const ToggleContainer = styled.div<{ isToggle: boolean }>`
cursor: pointer;
background-color: ${({ isDarkMode }) => (isDarkMode ? '#333' : '#ddd')};
background-color: ${({ isToggle }) => (isToggle ? '#333' : '#ddd')};
border-radius: 1rem;
padding: 0.2rem;
display: flex;
Expand All @@ -118,17 +118,17 @@ export const ToggleContainer = styled.div<{ isDarkMode: boolean }>`
height: 2.25rem;
position: relative;
transition: background-color 0.2s ease;
border: ${({ isDarkMode }) => (isDarkMode ? '0.0625rem solid #555' : '0.0625rem solid #aaa')};
border: ${({ isToggle }) => (isToggle ? '0.0625rem solid #555' : '0.0625rem solid #aaa')};
`

export const ToggleKnob = styled.div<{ isDarkMode: boolean }>`
export const ToggleKnob = styled.div<{ isToggle: boolean }>`
background-color: #ffffff;
border-radius: 50%;
width: 1.8rem;
height: 1.8rem;
position: absolute;
transition: transform 0.2s ease;
${({ isDarkMode }) => isDarkMode && `transform: translateX(2.7rem);`}
${({ isToggle }) => isToggle && `transform: translateX(2.7rem);`}
`

export const ButtonContainer = styled.div<ThemeInterface>`
Expand Down
73 changes: 50 additions & 23 deletions client/src/components/Modal/SettingModal/SettingModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,35 @@ import { useRecoilState } from 'recoil'
import * as styles from './SettingModal.styles'
import { ThemeFlag } from '@/types/theme'
import { themeState } from '@/states/theme'
import { filterState } from '@/states/filter'
import { userState } from '@/states/user'

interface SettingModalProps {
onConfirm: () => void
onConfirm: (changeUser: boolean) => void
}

const SettingModal = ({ onConfirm }: SettingModalProps) => {
const [currentTheme, setTheme] = useRecoilState(themeState)
const [filter, setFilter] = useRecoilState(filterState)
const [user, setUser] = useRecoilState(userState)
const [id, setId] = useState<string>(user.id)
const [nickname, setNickname] = useState<string>(user.nickname)
const [changeUser, setChangeUser] = useState<boolean>(false)
const [streamKey, setStreamKey] = useState<string>('')
const [streamLink, setStreamLink] = useState<string>('')
const isDarkMode = currentTheme === ThemeFlag.dark
const isFilter = filter === true

const onToggleContainer = () => {
const onThemeToggle = () => {
setTheme(isDarkMode ? ThemeFlag.light : ThemeFlag.dark)
localStorage.setItem('theme', `${currentTheme}`)
}

const onFilterToggle = () => {
setFilter(isFilter ? false : true)
localStorage.setItem('filter', `${filter}`)
}

const onIdInputButton = () => {
if (id.trim() === '') {
setId(user.id)
Expand Down Expand Up @@ -123,19 +132,32 @@ const SettingModal = ({ onConfirm }: SettingModalProps) => {
})
}

const onKeyInputButton = () => {
navigator.clipboard
.writeText(streamKey)
.then(() => {
alert('방송 비밀 키가 클립보드에 복사되었습니다.')
})
.catch((err) => {
console.error(err)
alert('방송 비밀키 복사에 실패 했습니다.')
})
const onCopyButton = (copy: 'key' | 'link') => {
if (copy === 'key') {
navigator.clipboard
.writeText(streamKey)
.then(() => {
alert('방송 비밀 키가 클립보드에 복사되었습니다.')
})
.catch((err) => {
console.error(err)
alert('방송 비밀키 복사에 실패 했습니다.')
})
} else if (copy === 'link') {
navigator.clipboard
.writeText(streamLink)
.then(() => {
alert('방송 송출 링크가 클립보드에 복사되었습니다.')
})
.catch((err) => {
console.error(err)
alert('방송 송출 링크 복사에 실패 했습니다.')
})
}
}

useEffect(() => {
setStreamLink('rtmp://stream.gbs-live.site/live')
if (user.id !== '') {
fetch(`${import.meta.env.VITE_API_URL}` + '/stream-keys/me', { method: 'GET', credentials: 'include' })
.then((res) => {
Expand All @@ -155,10 +177,7 @@ const SettingModal = ({ onConfirm }: SettingModalProps) => {
return (
<styles.Backdrop
onClick={() => {
onConfirm()
if (changeUser === true) {
window.location.reload()
}
onConfirm(changeUser)
}}
>
<styles.ModalContainer>
Expand Down Expand Up @@ -201,7 +220,14 @@ const SettingModal = ({ onConfirm }: SettingModalProps) => {
<styles.BodyText>방송 비밀 키</styles.BodyText>
<styles.InputContainer>
<styles.Input value={streamKey} type="password" readOnly={true} currentTheme={currentTheme} />
<styles.InputButton onClick={onKeyInputButton} currentTheme={currentTheme}>
<styles.InputButton onClick={() => onCopyButton('key')} currentTheme={currentTheme}>
복사하기
</styles.InputButton>
</styles.InputContainer>
<styles.BodyText>방송 송출 링크</styles.BodyText>
<styles.InputContainer>
<styles.Input value={streamLink} type="password" readOnly={true} currentTheme={currentTheme} />
<styles.InputButton onClick={() => onCopyButton('link')} currentTheme={currentTheme}>
복사하기
</styles.InputButton>
</styles.InputContainer>
Expand All @@ -211,19 +237,20 @@ const SettingModal = ({ onConfirm }: SettingModalProps) => {
<styles.HeaderText>환경설정</styles.HeaderText>
<styles.SettingContainer>
<styles.BodyText>다크모드</styles.BodyText>
<styles.ToggleContainer isDarkMode={isDarkMode} onClick={onToggleContainer}>
<styles.ToggleKnob isDarkMode={isDarkMode} />
<styles.ToggleContainer isToggle={isDarkMode} onClick={onThemeToggle}>
<styles.ToggleKnob isToggle={isDarkMode} />
</styles.ToggleContainer>
<styles.BodyText>자동 채팅 필터링</styles.BodyText>
<styles.ToggleContainer isToggle={isFilter} onClick={onFilterToggle}>
<styles.ToggleKnob isToggle={isFilter} />
</styles.ToggleContainer>
</styles.SettingContainer>
</styles.BlockContainer>
</styles.BodyContainer>
<styles.ButtonContainer currentTheme={currentTheme}>
<styles.Button
onClick={() => {
onConfirm()
if (changeUser === true) {
window.location.reload()
}
onConfirm(changeUser)
}}
>
확인
Expand Down
1 change: 1 addition & 0 deletions client/src/pages/BroadcastPage/BroadcastPage.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const Chatting = styled.div<ThemeInterface>`
export const ChattingList = styled.div`
display: flex;
flex-direction: column-reverse;
overflow-x: hidden;
overflow-y: scroll;
width: 100%;
height: 43.75rem;
Expand Down
27 changes: 19 additions & 8 deletions client/src/pages/BroadcastPage/BroadcastPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@ import ViewerModal from '@components/Modal/ViewerModal/ViewerModal'
import ConfirmModal from '@components/Modal/ConfirmModal/ConfirmModal'
import Chatting from '@components/Chatting/Chatting'
import { themeState } from '@/states/theme'
import { filterState } from '@/states/filter'
import { userState } from '@/states/user'
import HlsPlayer from '@components/HlsPlayer/HlsPlayer'

interface ViewerModalProps {
interface ViewerModalInterface {
nickname: string
authority: 'viewer' | 'streamer'
target: 'viewer' | 'streamer'
top: number
left: number
}

interface ChattingProps {
interface ChattingInterface {
nickname: string
message: string
}
Expand All @@ -42,18 +43,26 @@ const BroadcastPage = () => {
const [settingModal, setSettingModal] = useState<boolean>(false)
const [loginModal, setLoginModal] = useState<boolean>(false)
const [viewerModal, setViewerModal] = useState<boolean>(false)
const [viewerModalInfo, setViewerModalInfo] = useState<ViewerModalProps>({ nickname: '', authority: 'viewer', target: 'viewer', top: 0, left: 0 })
const [viewerModalInfo, setViewerModalInfo] = useState<ViewerModalInterface>({ nickname: '', authority: 'viewer', target: 'viewer', top: 0, left: 0 })
const [chatting, setChatting] = useState<string>('')
const [chattingList, setChattingList] = useState<Array<ChattingProps>>([])
const [chattingList, setChattingList] = useState<Array<ChattingInterface>>([])
const [confirmModal, setConfirmModal] = useState<boolean>(false)
const [confirmModalMessage, setConfirmModalMessage] = useState<string>('')
const [streamer, setStreamer] = useState<StreamerInterface>({ title: '', nickname: '', viewer: 0 })
const socket = useRef<any>(null)
const theme = useRecoilValue(themeState)
const filter = useRecoilValue(filterState)
const user = useRecoilValue(userState)

const onSetting = () => {
const onSetting = (changeUser: boolean) => {
setSettingModal(() => !settingModal)

if (changeUser === true) {
socket.current = io(`${import.meta.env.VITE_API_URL}`, { withCredentials: true })
if (socket.current) {
socket.current.disconnect()
}
}
}

const onLogin = () => {
Expand Down Expand Up @@ -100,13 +109,15 @@ const BroadcastPage = () => {
setConfirmModalMessage('채팅을 입력해주신 후 보내주세요.')
setConfirmModal(true)
} else {
socket.current.emit('chat', { message: chatting })
socket.current.emit('chat', { message: chatting, useFilter: filter })
}
setChatting('')
}

const onEnter = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (event.key === 'Enter' && event.shiftKey === false) {
if (event.nativeEvent.isComposing) {
return
} else if (event.key === 'Enter' && event.shiftKey === false) {
event.preventDefault()
onSend()
}
Expand Down Expand Up @@ -164,7 +175,7 @@ const BroadcastPage = () => {
getStreamer()
socket.current = io(`${import.meta.env.VITE_API_URL}`, { withCredentials: true })
socket.current.emit('join', { room: id })
socket.current.on('chat', (chatting: ChattingProps) => {
socket.current.on('chat', (chatting: ChattingInterface) => {
setChattingList((chattingList) => [chatting, ...chattingList])
})
socket.current.on('kick', (kickInfo: KickInterface) => {
Expand Down
6 changes: 6 additions & 0 deletions client/src/states/filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { atom } from 'recoil'

export const filterState = atom<boolean>({
key: 'FILTER_STATE',
default: localStorage.getItem('filter') === `${false}` ? false : true,
})
4 changes: 2 additions & 2 deletions client/src/states/user.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { atom } from 'recoil'
import { User } from '@/types/user'
import { UserInterface } from '@/types/user'

const getUser = () => {
const user = localStorage.getItem('user')
Expand All @@ -11,7 +11,7 @@ const getUser = () => {
}
}

export const userState = atom<User>({
export const userState = atom<UserInterface>({
key: 'USER_STATE',
default: {
id: `${getUser() === null ? '' : getUser().id}`,
Expand Down
2 changes: 1 addition & 1 deletion client/src/types/user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface User {
export interface UserInterface {
id: string
nickname: string
}
2 changes: 1 addition & 1 deletion server/api-server/src/chat/chat.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
): Promise<ChatPayload> {
this.logger.debug('Chat payload: ', payload);

const result = await this.aiChatFilter.filter(payload.message);
const result = payload.useFilter ? await this.aiChatFilter.filter(payload.message) : { message : payload.message };

this.server.to(client.data.room).emit('chat', {
id: client.data.userId,
Expand Down
1 change: 1 addition & 0 deletions server/api-server/src/chat/dto/chat-payload.dto.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export class ChatPayload {
message: string;
useFilter: boolean;
}

0 comments on commit 33d6488

Please sign in to comment.