Files
xiaoxia-saas/apps/web/src/pages/editing-planner/components/TtsPanel/index.tsx
T
前端Agent b7ef9fed14
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 24s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 44s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 42s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m16s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m46s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 3m6s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 55s
AI Code Review / AI Code Review (pull_request) Successful in 2m55s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m14s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m28s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m38s
CI/CD Pipeline / CI Gate (pull_request) Successful in 35s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 33s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 36s
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 1102h58m7s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 1102h58m11s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 1102h58m32s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 1102h58m34s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 1102h58m36s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 1102h58m57s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 1102h59m1s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 1102h58m59s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 1103h1m13s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 1103h1m17s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 1103h1m18s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 1103h2m42s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 1103h2m44s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 1103h31m23s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 1103h32m13s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 1103h34m29s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 1103h35m54s
fix(#1196): TTS面板克隆音色试听改用/voice-clones/{id}/preview接口
- VoiceSelector: 克隆音色试听按钮加loading态,点击时显示合成中
- useTtsPanel: handlePlayVoiceSample接收完整voice对象,区分preset/clone
- 克隆音色调用/voice-clones/{id}/preview接口实时合成试听音频
- 预设音色保持原有逻辑直接播放preview_url
- 统一错误处理和竞态防护
2026-08-01 16:22:48 +08:00

181 lines
5.4 KiB
TypeScript

/**
* TTS 配音面板 — Drawer 形式
* 配音模式切换 + 文本输入 + 音色选择(预设+克隆) + 语速/语调/音量 + 试听 + 字幕联动
*/
import React from "react"
import { Drawer } from "antd"
import type { TtsConfig } from "../../types"
import { useTtsPanel } from "./hooks/useTtsPanel"
import { VoiceSelector } from "./components/VoiceSelector"
import { TtsSlider } from "./components/TtsSlider"
import { TTS_MODE_OPTIONS } from "./constants"
export interface TtsPanelProps {
open: boolean
onClose: () => void
/** 当前片段 TTS 配置 */
config: TtsConfig
onChange: (config: TtsConfig) => void
}
const TtsPanel: React.FC<TtsPanelProps> = ({ open, onClose, config, onChange }) => {
const {
presetVoices,
cloneVoices,
voicesLoading,
previewLoading,
playingVoiceId,
loadingSampleVoiceId,
handleModeChange,
handleTextChange,
handleVoiceSelect,
handlePlayVoiceSample,
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={handleTextChange}
maxLength={5000}
rows={5}
/>
</div>
{/* 音色选择 */}
<VoiceSelector
presetVoices={presetVoices}
cloneVoices={cloneVoices}
selectedVoiceId={config.voice_id}
loading={voicesLoading}
playingVoiceId={playingVoiceId}
loadingSampleVoiceId={loadingSampleVoiceId}
onSelect={handleVoiceSelect}
onPlaySample={handlePlayVoiceSample}
/>
{/* 语速 */}
<TtsSlider
label="语速"
value={config.speed}
min={0.5}
max={2.0}
step={0.05}
onChange={handleSpeedChange}
formatter={(v) => `${v.toFixed(2)}x`}
marks={["0.5x", "1.0x", "2.0x"]}
/>
{/* 语调 */}
<TtsSlider
label="语调"
value={config.pitch}
min={-12}
max={12}
step={1}
onChange={handlePitchChange}
formatter={(v) => `${v > 0 ? "+" : ""}${v} 半音`}
marks={["-12", "0", "+12"]}
/>
{/* 音量 */}
<TtsSlider
label="音量"
value={config.volume}
min={0}
max={100}
step={1}
onChange={handleVolumeChange}
formatter={(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