Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 55 additions & 4 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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() {
Expand Down Expand Up @@ -245,10 +260,46 @@ function ReportsPage() {
}

function SettingsPage() {
const { settings, setTtsVoice } = useSettings()

return (
<Container>
<Typography variant="h4">설정</Typography>
<Typography color="text.secondary">계정 및 앱 설정</Typography>
<Container maxWidth="md">
<Box sx={{ mb: 4 }}>
<Typography variant="h4" fontWeight={700} gutterBottom>
설정
</Typography>
<Typography variant="body1" color="text.secondary">
앱 설정을 변경할 수 있습니다
</Typography>
</Box>

<Card>
<CardContent>
<FormControl component="fieldset">
<FormLabel component="legend" sx={{ fontWeight: 600, mb: 1 }}>
TTS 음성 선택
</FormLabel>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
채팅에서 메시지를 읽어줄 음성을 선택하세요
</Typography>
<RadioGroup
value={settings.ttsVoice}
onChange={(e) => setTtsVoice(e.target.value)}
>
<FormControlLabel
value="FEMALE"
control={<Radio />}
label="여성 음성"
/>
<FormControlLabel
value="MALE"
control={<Radio />}
label="남성 음성"
/>
</RadioGroup>
</FormControl>
</CardContent>
</Card>
</Container>
)
}
Expand Down
42 changes: 42 additions & 0 deletions src/contexts/SettingsContext.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<SettingsContext.Provider value={{ settings, updateSettings, setTtsVoice }}>
{children}
</SettingsContext.Provider>
)
}

export const useSettings = () => {
const context = useContext(SettingsContext)
if (!context) {
throw new Error('useSettings must be used within a SettingsProvider')
}
return context
}
4 changes: 2 additions & 2 deletions src/domains/chat/services/chatService.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
},
}

Expand Down
4 changes: 3 additions & 1 deletion src/domains/freetalk/components/ChatRoomModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: '초급' },
Expand All @@ -29,6 +30,7 @@ const levelColors = {
}

const ChatRoomModal = ({ open, onClose, room, onLeave }) => {
const { settings } = useSettings()
const messagesEndRef = useRef(null)
const dragRef = useRef(null)

Expand Down Expand Up @@ -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)
Expand Down
9 changes: 6 additions & 3 deletions src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@ 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(
<StrictMode>
<Provider store={store}>
<BrowserRouter>
<ThemeProvider>
<ChatProvider>
<App />
</ChatProvider>
<SettingsProvider>
<ChatProvider>
<App />
</ChatProvider>
</SettingsProvider>
</ThemeProvider>
</BrowserRouter>
</Provider>
Expand Down