Commands/Dictionary 미연결 버그 4건 수정
1. CommandsPage: handleSave/handleDelete에 실제 IPC 호출 연결 - instruction:create/update/delete 호출 추가 - 프리셋은 삭제 불가 (isBuiltin 체크) 2. Preload: instruction API 추가 (getAll/create/update/delete/reorder) - 기존에 preload 브릿지가 없어서 렌더러에서 호출 불가했음 3. Dictionary → STT initialPrompt 주입 - VoiceModeService._transcribe에서 DictionaryService.getPromptHints() 호출 - 사용자 사전 단어를 Whisper initialPrompt로 전달 4. DictionaryPage: 편집 기능 추가 - 각 항목에 Edit 버튼 추가 - Add/Edit 공용 다이얼로그 (editId로 분기)
This commit is contained in:
parent
6e9e8b1f29
commit
5596a52dcb
4 changed files with 185 additions and 44 deletions
|
|
@ -5,6 +5,7 @@ import { useState, useEffect, useCallback } from 'react'
|
|||
import { Box, TextField, Button, IconButton, Dialog, DialogTitle, DialogContent, DialogActions, InputAdornment } from '@mui/material'
|
||||
import AddIcon from '@mui/icons-material/Add'
|
||||
import DeleteIcon from '@mui/icons-material/Delete'
|
||||
import EditIcon from '@mui/icons-material/Edit'
|
||||
import SearchIcon from '@mui/icons-material/Search'
|
||||
import { MetalCard, PhosphorText, PhysicalButton } from '../components/ds'
|
||||
import { d3roPalette, d3roFontMono } from '../theme'
|
||||
|
|
@ -16,9 +17,10 @@ export function DictionaryPage(): React.ReactElement {
|
|||
const [data, setData] = useState<DictPageData | null>(null)
|
||||
const [search, setSearch] = useState('')
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [addOpen, setAddOpen] = useState(false)
|
||||
const [newWord, setNewWord] = useState('')
|
||||
const [newPronunciation, setNewPronunciation] = useState('')
|
||||
const [dialogOpen, setDialogOpen] = useState(false)
|
||||
const [editId, setEditId] = useState<string | null>(null)
|
||||
const [formWord, setFormWord] = useState('')
|
||||
const [formPronunciation, setFormPronunciation] = useState('')
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
setLoading(true)
|
||||
|
|
@ -31,10 +33,36 @@ export function DictionaryPage(): React.ReactElement {
|
|||
|
||||
useEffect(() => { loadData() }, [loadData])
|
||||
|
||||
const handleAdd = async () => {
|
||||
if (!newWord.trim()) return
|
||||
await window.electronAPI.dictionary.add({ word: newWord.trim(), pronunciation: newPronunciation.trim() || undefined })
|
||||
setNewWord(''); setNewPronunciation(''); setAddOpen(false); loadData()
|
||||
const openAdd = () => {
|
||||
setEditId(null)
|
||||
setFormWord('')
|
||||
setFormPronunciation('')
|
||||
setDialogOpen(true)
|
||||
}
|
||||
|
||||
const openEdit = (entry: DictionaryEntry) => {
|
||||
setEditId(entry.id)
|
||||
setFormWord(entry.word)
|
||||
setFormPronunciation(entry.pronunciation ?? '')
|
||||
setDialogOpen(true)
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!formWord.trim()) return
|
||||
if (editId) {
|
||||
await window.electronAPI.dictionary.update({
|
||||
id: editId,
|
||||
word: formWord.trim(),
|
||||
pronunciation: formPronunciation.trim() || undefined,
|
||||
})
|
||||
} else {
|
||||
await window.electronAPI.dictionary.add({
|
||||
word: formWord.trim(),
|
||||
pronunciation: formPronunciation.trim() || undefined,
|
||||
})
|
||||
}
|
||||
setDialogOpen(false)
|
||||
loadData()
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -43,7 +71,7 @@ export function DictionaryPage(): React.ReactElement {
|
|||
<PhosphorText variant="label" sx={{ color: d3roPalette.text.inactive }}>
|
||||
CUSTOM DICTIONARY — {data?.total ?? 0} WORDS
|
||||
</PhosphorText>
|
||||
<Button variant="contained" startIcon={<AddIcon />} onClick={() => setAddOpen(true)} size="small">
|
||||
<Button variant="contained" startIcon={<AddIcon />} onClick={openAdd} size="small">
|
||||
ADD
|
||||
</Button>
|
||||
</Box>
|
||||
|
|
@ -81,25 +109,31 @@ export function DictionaryPage(): React.ReactElement {
|
|||
{entry.category.toUpperCase()} · {entry.usageCount}× USED
|
||||
</Box>
|
||||
</Box>
|
||||
<IconButton size="small" onClick={() => { window.electronAPI.dictionary.delete({ id: entry.id }); loadData() }}
|
||||
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.tag.red } }}>
|
||||
<DeleteIcon sx={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
<Box sx={{ display: 'flex', gap: 0.5 }}>
|
||||
<IconButton size="small" onClick={() => openEdit(entry)}
|
||||
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.accent.amber } }}>
|
||||
<EditIcon sx={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
<IconButton size="small" onClick={() => { window.electronAPI.dictionary.delete({ id: entry.id }); loadData() }}
|
||||
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.tag.red } }}>
|
||||
<DeleteIcon sx={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</MetalCard>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Dialog open={addOpen} onClose={() => setAddOpen(false)} maxWidth="xs" fullWidth>
|
||||
<DialogTitle sx={{ fontWeight: 700 }}>Add Word</DialogTitle>
|
||||
<Dialog open={dialogOpen} onClose={() => setDialogOpen(false)} maxWidth="xs" fullWidth>
|
||||
<DialogTitle sx={{ fontWeight: 700 }}>{editId ? '단어 편집' : '단어 추가'}</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField label="Word" value={newWord} onChange={(e) => setNewWord(e.target.value)} fullWidth autoFocus sx={{ mt: 1 }} />
|
||||
<TextField label="Pronunciation (optional)" value={newPronunciation} onChange={(e) => setNewPronunciation(e.target.value)} fullWidth sx={{ mt: 2 }} />
|
||||
<TextField label="단어" value={formWord} onChange={(e) => setFormWord(e.target.value)} fullWidth autoFocus sx={{ mt: 1 }} />
|
||||
<TextField label="발음 (선택)" value={formPronunciation} onChange={(e) => setFormPronunciation(e.target.value)} fullWidth sx={{ mt: 2 }} />
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||
<Button onClick={() => setAddOpen(false)} color="secondary" variant="contained">Cancel</Button>
|
||||
<Button onClick={handleAdd} variant="contained" disabled={!newWord.trim()}>Add</Button>
|
||||
<Button onClick={() => setDialogOpen(false)} sx={{ color: d3roPalette.text.inactive }}>취소</Button>
|
||||
<Button onClick={handleSave} variant="contained" disabled={!formWord.trim()}>저장</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue