daa0e1f7b5
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
175 lines
5.3 KiB
TypeScript
Executable File
175 lines
5.3 KiB
TypeScript
Executable File
/**
|
|
* TTS 配音面板 — Drawer 形式
|
|
* 配音模式切换 + 文本输入 + 音色选择 + 语速/语调/音量 + 试听 + 字幕联动
|
|
*/
|
|
import React from "react"
|
|
import { Drawer } from "antd"
|
|
import type { TtsConfig } from "@/pages/editing-planner/types"
|
|
import { TTS_MODE_OPTIONS } from "@/pages/editing-planner/constants/tts"
|
|
import VoiceSelector from "./tts/VoiceSelector"
|
|
import TtsSlider from "./tts/TtsSlider"
|
|
import { useTtsPanel } from "@/pages/editing-planner/hooks/useTtsPanel"
|
|
|
|
interface TtsPanelProps {
|
|
open: boolean
|
|
onClose: () => void
|
|
/** 当前片段 TTS 配置 */
|
|
config: TtsConfig
|
|
onChange: (config: TtsConfig) => void
|
|
}
|
|
|
|
const TtsPanel: React.FC<TtsPanelProps> = ({ open, onClose, config, onChange }) => {
|
|
const {
|
|
voices,
|
|
voicesLoading,
|
|
previewLoading,
|
|
handleModeChange,
|
|
handleTextChange,
|
|
handleVoiceSelect,
|
|
handleSpeedChange,
|
|
handlePitchChange,
|
|
handleVolumeChange,
|
|
handleSubtitleSyncToggle,
|
|
handlePreview,
|
|
handleReset,
|
|
handleClose,
|
|
} = useTtsPanel({ open, config, onChange, onClose })
|
|
|
|
return (
|
|
<Drawer
|
|
title="🎙️ TTS 配音"
|
|
placement="right"
|
|
width={400}
|
|
open={open}
|
|
onClose={handleClose}
|
|
className="tts-panel-drawer"
|
|
>
|
|
{/* ── 配音模式切换 ── */}
|
|
<div className="tts-mode-section">
|
|
<div className="tts-mode-label">配音模式</div>
|
|
<div className="tts-mode-group">
|
|
{TTS_MODE_OPTIONS.map((m) => (
|
|
<button
|
|
key={m.mode}
|
|
className={`tts-mode-btn${config.mode === m.mode ? " active" : ""}`}
|
|
onClick={() => handleModeChange(m.mode)}
|
|
>
|
|
<span className="tts-mode-icon">{m.icon}</span>
|
|
<span className="tts-mode-text">{m.label}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* ── TTS 配置(仅 tts 模式显示) ── */}
|
|
{config.mode === "tts" && (
|
|
<>
|
|
{/* 文本输入 */}
|
|
<div className="tts-text-section">
|
|
<div className="tts-text-header">
|
|
<span className="tts-text-label">合成文本</span>
|
|
<span className="tts-text-count">{config.text.length}/5000</span>
|
|
</div>
|
|
<textarea
|
|
className="tts-text-input"
|
|
placeholder="请输入需要合成的文本内容..."
|
|
value={config.text}
|
|
onChange={(e) => handleTextChange(e.target.value)}
|
|
maxLength={5000}
|
|
rows={5}
|
|
/>
|
|
</div>
|
|
|
|
{/* 音色选择 */}
|
|
<VoiceSelector
|
|
voices={voices}
|
|
voicesLoading={voicesLoading}
|
|
selectedVoiceId={config.voice_id}
|
|
onVoiceSelect={handleVoiceSelect}
|
|
/>
|
|
|
|
{/* 语速滑块 */}
|
|
<TtsSlider
|
|
label="语速"
|
|
value={config.speed}
|
|
min={0.5}
|
|
max={2.0}
|
|
step={0.05}
|
|
unit="x"
|
|
onChange={handleSpeedChange}
|
|
marks={["0.5x", "1.0x", "2.0x"]}
|
|
tooltipFormatter={(v) => `${v.toFixed(2)}x`}
|
|
/>
|
|
|
|
{/* 语调滑块 */}
|
|
<TtsSlider
|
|
label="语调"
|
|
value={config.pitch}
|
|
min={-12}
|
|
max={12}
|
|
step={1}
|
|
unit="半音"
|
|
onChange={handlePitchChange}
|
|
marks={["-12", "0", "+12"]}
|
|
tooltipFormatter={(v) => `${v}半音`}
|
|
/>
|
|
|
|
{/* 音量滑块 */}
|
|
<TtsSlider
|
|
label="音量"
|
|
value={config.volume}
|
|
min={0}
|
|
max={100}
|
|
step={1}
|
|
unit="%"
|
|
onChange={handleVolumeChange}
|
|
tooltipFormatter={(v) => `${v}%`}
|
|
/>
|
|
|
|
{/* 试听按钮 */}
|
|
<div className="tts-preview-section">
|
|
<button className="tts-preview-btn" onClick={handlePreview} disabled={previewLoading}>
|
|
{previewLoading ? "⏳ 生成中..." : "🔊 试听"}
|
|
</button>
|
|
</div>
|
|
|
|
{/* 字幕联动 */}
|
|
<div className="tts-subtitle-section">
|
|
<div className="tts-subtitle-info">
|
|
<span className="tts-subtitle-label">字幕联动</span>
|
|
<span className="tts-subtitle-desc">
|
|
{config.subtitle_sync ? "TTS 文本自动同步到字幕" : "字幕需手动编辑"}
|
|
</span>
|
|
</div>
|
|
<div
|
|
className={`ep-toggle${config.subtitle_sync ? " active" : ""}`}
|
|
onClick={handleSubtitleSyncToggle}
|
|
>
|
|
<div className="ep-toggle-knob" />
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* ── 上传配音模式提示 ── */}
|
|
{config.mode === "upload" && (
|
|
<div className="tts-upload-hint">
|
|
<p>请在右侧面板的「配音素材」中选择已上传的配音文件。</p>
|
|
<p>如需上传新配音,请前往配音库页面。</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* ── 底部操作 ── */}
|
|
{config.mode === "tts" && (
|
|
<div className="tts-footer">
|
|
<button className="tts-reset-btn" onClick={handleReset}>
|
|
重置默认
|
|
</button>
|
|
</div>
|
|
)}
|
|
</Drawer>
|
|
)
|
|
}
|
|
|
|
export default TtsPanel
|