Phase 15.5 구현: 화자 구분 (Speaker Diarization)
- Sidecar: pyannote /diarize 엔드포인트 + requirements.txt 업데이트 - LLM 기반 화자 추정 (Phase 1 — 오디오 보존 없이 전사 텍스트 분석) - CaptionSegment에 speaker 필드 추가 - EditableSegment: 화자별 색상 바 + 화자 Chip 표시 - TranscriptTab: 화자 구분 버튼 + 진행률 - 설정: HuggingFace 토큰 입력 + Diarization 토글 - IPC: DIARIZE + DIARIZATION_PROGRESS 채널 - 에러코드: 895-897 - 12개 locale i18n
This commit is contained in:
parent
da25791c75
commit
fc7628327a
25 changed files with 679 additions and 18 deletions
|
|
@ -2,10 +2,26 @@
|
|||
// Phase 14.5: 전사 세그먼트 인라인 편집 컴포넌트
|
||||
|
||||
import { useState, useRef, useCallback } from 'react'
|
||||
import { Box, TextField, Tooltip } from '@mui/material'
|
||||
import { Box, TextField, Tooltip, Chip } from '@mui/material'
|
||||
import { d3roPalette, d3roFontMono } from '../../theme'
|
||||
import { useI18n } from '../../i18n'
|
||||
|
||||
// Phase 15.5: 화자별 색상 매핑 (d3roPalette SSOT)
|
||||
const SPEAKER_COLORS = [
|
||||
d3roPalette.tag.purple,
|
||||
d3roPalette.tag.green,
|
||||
d3roPalette.tag.orange,
|
||||
d3roPalette.tag.red,
|
||||
'#3b82f6',
|
||||
]
|
||||
|
||||
function getSpeakerColor(speaker: string): string {
|
||||
// "화자 1" → 0, "화자 2" → 1, ...
|
||||
const match = /(\d+)$/.exec(speaker)
|
||||
const idx = match ? (parseInt(match[1], 10) - 1) : 0
|
||||
return SPEAKER_COLORS[idx % SPEAKER_COLORS.length]
|
||||
}
|
||||
|
||||
interface EditableSegmentProps {
|
||||
segmentId: string
|
||||
timestamp: number // ms, 상대 시간
|
||||
|
|
@ -13,6 +29,7 @@ interface EditableSegmentProps {
|
|||
edited: boolean // 수정 여부
|
||||
onEdit: (segmentId: string, newText: string) => void
|
||||
readOnly?: boolean
|
||||
speaker?: string
|
||||
}
|
||||
|
||||
function formatTimestamp(ms: number): string {
|
||||
|
|
@ -29,6 +46,7 @@ export function EditableSegment({
|
|||
edited,
|
||||
onEdit,
|
||||
readOnly = false,
|
||||
speaker,
|
||||
}: EditableSegmentProps): React.ReactElement {
|
||||
const { t } = useI18n()
|
||||
const [editing, setEditing] = useState(false)
|
||||
|
|
@ -61,9 +79,20 @@ export function EditableSegment({
|
|||
[handleCommit],
|
||||
)
|
||||
|
||||
const speakerColor = speaker ? getSpeakerColor(speaker) : undefined
|
||||
|
||||
if (editing) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1, mb: 0.5 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
gap: 1,
|
||||
mb: 0.5,
|
||||
borderLeft: speakerColor ? `4px solid ${speakerColor}` : undefined,
|
||||
pl: speakerColor ? 0.75 : 0,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
|
|
@ -125,7 +154,16 @@ export function EditableSegment({
|
|||
)
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1, mb: 0.5 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
gap: 1,
|
||||
mb: 0.5,
|
||||
borderLeft: speakerColor ? `4px solid ${speakerColor}` : undefined,
|
||||
pl: speakerColor ? 0.75 : 0,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
|
|
@ -139,6 +177,21 @@ export function EditableSegment({
|
|||
>
|
||||
{formatTimestamp(timestamp)}
|
||||
</Box>
|
||||
{speaker && (
|
||||
<Chip
|
||||
label={speaker}
|
||||
size="small"
|
||||
sx={{
|
||||
fontSize: 10,
|
||||
height: 18,
|
||||
flexShrink: 0,
|
||||
bgcolor: `${speakerColor}22`,
|
||||
color: speakerColor,
|
||||
border: `1px solid ${speakerColor}55`,
|
||||
fontFamily: d3roFontMono,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{edited ? (
|
||||
<Tooltip title={t('meeting.modified')} placement="top">
|
||||
{textNode}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue