From d61878b61a8cd6ca58b7c7d88c59635b3a5eb607 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Sun, 19 Jul 2026 09:46:59 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(P1):=20=E6=99=BA=E8=83=BD=E5=89=AA?= =?UTF-8?q?=E8=BE=91=E9=85=8D=E9=9F=B3=E6=94=AF=E6=8C=81=E4=BB=8E=E9=85=8D?= =?UTF-8?q?=E9=9F=B3=E5=BA=93=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 配音选择新增'从配音库选择'模式 - 拉取用户配音库素材列表,支持选择、试听 - 选中后通过 custom_audio_url 传给后端生成 - 支持时长显示和点击选中高亮 --- apps/web/src/pages/generate/GeneratePage.tsx | 157 ++++++++++++++++++- 1 file changed, 155 insertions(+), 2 deletions(-) diff --git a/apps/web/src/pages/generate/GeneratePage.tsx b/apps/web/src/pages/generate/GeneratePage.tsx index 94bc215fc..68dc95e3b 100755 --- a/apps/web/src/pages/generate/GeneratePage.tsx +++ b/apps/web/src/pages/generate/GeneratePage.tsx @@ -24,7 +24,7 @@ import { CloseOutlined, } from "@ant-design/icons" import type { AssetItem } from "@/api/assets" -import { getAssets, getAssetLibraries } from "@/api/assets" +import { getAssets, getAssetLibraries, getAssetsByKind } from "@/api/assets" import { createEditPlan, generateEditPlan, @@ -129,8 +129,15 @@ const GeneratePage: React.FC = () => { /* ── 配音 ── */ const [selectedVoice, setSelectedVoice] = useState("") const [playingVoice, setPlayingVoice] = useState(null) - const [voiceMode, setVoiceMode] = useState<"preset" | "custom" | "clone">("preset") + const [voiceMode, setVoiceMode] = useState<"preset" | "custom" | "clone" | "library">("preset") const [customVoiceText, setCustomVoiceText] = useState("") + /* ── 从配音库选择 ── */ + const [selectedVoiceAssetId, setSelectedVoiceAssetId] = useState("") + const { data: voiceAssets = [], isLoading: voiceAssetsLoading } = useQuery({ + queryKey: ["voice-assets-library"], + queryFn: () => getAssetsByKind("voice"), + staleTime: 60_000, + }) /* ── 生成数量 ── */ const [generateCount, setGenerateCount] = useState(1) @@ -465,6 +472,10 @@ const GeneratePage: React.FC = () => { message.warning("请先选择一个克隆音色") return } + if (voiceMode === "library" && !selectedVoiceAssetId) { + message.warning("请从配音库选择一个配音") + return + } setGenerating(true) setProgress(0) @@ -484,6 +495,11 @@ const GeneratePage: React.FC = () => { voiceConfig.voice_id = selectedVoice || undefined if (customAudioUrl) voiceConfig.custom_audio_url = customAudioUrl if (customVoiceText.trim()) voiceConfig.custom_text = customVoiceText.trim() + } else if (voiceMode === "library") { + const voiceAsset = voiceAssets.find((a) => a.id === selectedVoiceAssetId) + if (voiceAsset?.file_url) { + voiceConfig.custom_audio_url = voiceAsset.file_url + } } const plan = await createEditPlan({ @@ -761,6 +777,10 @@ const GeneratePage: React.FC = () => { const cv = clonedVoices.find((v) => v.id === selectedClonedVoice) return cv ? cv.name : "未选择" } + if (voiceMode === "library") { + const va = voiceAssets.find((a) => a.id === selectedVoiceAssetId) + return va ? va.name : "未选择" + } const pv = presetVoices.find((v) => v.voice_id === selectedVoice) return pv ? pv.name : "未选择" } @@ -1114,6 +1134,32 @@ const GeneratePage: React.FC = () => {

上传语音样本克隆

+ {/* 从配音库选择 */} +
setVoiceMode("library")} + role="button" + tabIndex={0} + aria-pressed={voiceMode === "library"} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault() + setVoiceMode("library") + } + }} + > + +
+ 📁 +
+
+

从配音库选择

+

我上传的配音文件

+
+
{/* 预设音色详细列表(当 voiceMode === preset 时显示 API 返回的音色) */} @@ -1167,6 +1213,113 @@ const GeneratePage: React.FC = () => { )} + {/* 从配音库选择 */} + {voiceMode === "library" && ( +
+
+ + {voiceAssetsLoading ? ( +

加载中…

+ ) : voiceAssets.length === 0 ? ( +
+

+ 配音库暂无素材,去上传配音吧 +

+
+ ) : ( +
+ {voiceAssets.map((asset) => ( +
{ + setSelectedVoiceAssetId(asset.id) + }} + style={{ + padding: 12, + border: `2px solid ${ + selectedVoiceAssetId === asset.id + ? "var(--primary, #6366f1)" + : "var(--border-color, #e2e8f0)" + }`, + borderRadius: "var(--radius-sm, 10px)", + cursor: "pointer", + background: + selectedVoiceAssetId === asset.id + ? "rgba(99, 102, 241, 0.05)" + : "transparent", + }} + > +
+ 🎵 + + {asset.name} + +
+
+ + {asset.duration + ? `${Math.floor(asset.duration / 60)}:${String(asset.duration % 60).padStart(2, "0")}` + : "—"} + + {asset.file_url && ( + + )} +
+
+ ))} +
+ )} +
+
+ )} + {/* 自定义录制 */} {voiceMode === "custom" && (
-- 2.54.0 From 5043a043d5dc9bf8858f89204aad2ea28f97c916 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Sun, 19 Jul 2026 10:29:11 +0800 Subject: [PATCH 2/3] ci: retrigger checks -- 2.54.0 From e9bf48a34e7a8bc5992a3db94c7f4c776b7e4780 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Sun, 19 Jul 2026 10:51:06 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=E8=A1=A5=E5=85=A8handleGenerate?= =?UTF-8?q?=E7=9A=84useCallback=E4=BE=9D=E8=B5=96=20-=20voiceAssets?= =?UTF-8?q?=E5=92=8CselectedVoiceAssetId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit react-hooks/exhaustive-deps警告,--max-warnings 0会导致lint失败 --- apps/web/src/pages/generate/GeneratePage.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/web/src/pages/generate/GeneratePage.tsx b/apps/web/src/pages/generate/GeneratePage.tsx index 68dc95e3b..ff3baa14e 100755 --- a/apps/web/src/pages/generate/GeneratePage.tsx +++ b/apps/web/src/pages/generate/GeneratePage.tsx @@ -694,6 +694,8 @@ const GeneratePage: React.FC = () => { selectedVoice, voiceMode, selectedClonedVoice, + selectedVoiceAssetId, + voiceAssets, customAudioUrl, customVoiceText, videoRatio, -- 2.54.0