diff --git a/src/App.jsx b/src/App.jsx index 9a21099..4398e54 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,6 +1,20 @@ import { useState } from 'react' import { Routes, Route, useNavigate } from 'react-router-dom' -import { Box, Typography, Container, Card, CardContent, Grid, Button, Collapse, IconButton } from '@mui/material' +import { + Box, + Typography, + Container, + Card, + CardContent, + Grid, + Button, + Collapse, + FormControl, + FormLabel, + RadioGroup, + FormControlLabel, + Radio, +} from '@mui/material' import { Mic as SpeakingIcon, Create as WritingCategoryIcon, @@ -15,6 +29,7 @@ import FreetalkPeoplePage from './domains/freetalk/pages/FreetalkPeoplePage' import ChatRoomPage from './domains/freetalk/pages/ChatRoomPage' import ChatRoomModal from './domains/freetalk/components/ChatRoomModal' import { useChat } from './contexts/ChatContext' +import { useSettings } from './contexts/SettingsContext' // 임시 대시보드 페이지 function Dashboard() { @@ -245,10 +260,46 @@ function ReportsPage() { } function SettingsPage() { + const { settings, setTtsVoice } = useSettings() + return ( - - 설정 - 계정 및 앱 설정 + + + + 설정 + + + 앱 설정을 변경할 수 있습니다 + + + + + + + + TTS 음성 선택 + + + 채팅에서 메시지를 읽어줄 음성을 선택하세요 + + setTtsVoice(e.target.value)} + > + } + label="여성 음성" + /> + } + label="남성 음성" + /> + + + + ) } diff --git a/src/contexts/SettingsContext.jsx b/src/contexts/SettingsContext.jsx new file mode 100644 index 0000000..c831958 --- /dev/null +++ b/src/contexts/SettingsContext.jsx @@ -0,0 +1,42 @@ +import { createContext, useContext, useState, useCallback, useEffect } from 'react' + +const SettingsContext = createContext(null) + +const STORAGE_KEY = 'app_settings' + +const defaultSettings = { + ttsVoice: 'FEMALE', // MALE | FEMALE +} + +export const SettingsProvider = ({ children }) => { + const [settings, setSettings] = useState(() => { + const stored = localStorage.getItem(STORAGE_KEY) + return stored ? { ...defaultSettings, ...JSON.parse(stored) } : defaultSettings + }) + + useEffect(() => { + localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)) + }, [settings]) + + const updateSettings = useCallback((updates) => { + setSettings((prev) => ({ ...prev, ...updates })) + }, []) + + const setTtsVoice = useCallback((voice) => { + updateSettings({ ttsVoice: voice }) + }, [updateSettings]) + + return ( + + {children} + + ) +} + +export const useSettings = () => { + const context = useContext(SettingsContext) + if (!context) { + throw new Error('useSettings must be used within a SettingsProvider') + } + return context +} diff --git a/src/domains/chat/services/chatService.js b/src/domains/chat/services/chatService.js index 9ab3207..4d33f82 100644 --- a/src/domains/chat/services/chatService.js +++ b/src/domains/chat/services/chatService.js @@ -75,8 +75,8 @@ export const messageService = { // 음성 API export const voiceService = { // TTS 변환 - synthesize: async (text) => { - return chatApi.post('/chat/voice/synthesize', { text }) + synthesize: async (text, voice = 'FEMALE') => { + return chatApi.post('/chat/voice/synthesize', { text, voice }) }, } diff --git a/src/domains/freetalk/components/ChatRoomModal.jsx b/src/domains/freetalk/components/ChatRoomModal.jsx index 49ace7b..e5f6dde 100644 --- a/src/domains/freetalk/components/ChatRoomModal.jsx +++ b/src/domains/freetalk/components/ChatRoomModal.jsx @@ -21,6 +21,7 @@ import { OpenInFull as MaximizeIcon, } from '@mui/icons-material' import { chatRoomService, messageService, voiceService, TEMP_USER_ID } from '../../chat/services/chatService' +import { useSettings } from '../../../contexts/SettingsContext' const levelColors = { beginner: { bg: '#e8f5e9', color: '#2e7d32', label: '초급' }, @@ -29,6 +30,7 @@ const levelColors = { } const ChatRoomModal = ({ open, onClose, room, onLeave }) => { + const { settings } = useSettings() const messagesEndRef = useRef(null) const dragRef = useRef(null) @@ -179,7 +181,7 @@ const ChatRoomModal = ({ open, onClose, room, onLeave }) => { setPlayingTTS(messageId) try { - const response = await voiceService.synthesize(text) + const response = await voiceService.synthesize(text, settings.ttsVoice) const responseData = response.data || response if (responseData.audioUrl) { const audio = new Audio(responseData.audioUrl) diff --git a/src/main.jsx b/src/main.jsx index d6dd98d..3d4cc67 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -6,6 +6,7 @@ import App from './App.jsx' import { store } from './store' import { ThemeProvider } from './contexts/ThemeContext' import { ChatProvider } from './contexts/ChatContext' +import { SettingsProvider } from './contexts/SettingsContext' import './index.css' createRoot(document.getElementById('root')).render( @@ -13,9 +14,11 @@ createRoot(document.getElementById('root')).render( - - - + + + + +