"use client"; import { useEffect, useState } from "react"; import { audioContract, audioRequestSchema, buildAudioRequest, type AudioRequest, } from "@/lib/video-canvas/audio-contract"; type Model = { id: string; modelId: string; name: string; kind: string }; export function AudioGenerationComposer({ initial, connectedText = "", busy, onSubmit, onUpload, onDraft, }: { initial?: Record; connectedText?: string; busy: boolean; onSubmit: (params: AudioRequest, draft: AudioRequest) => Promise; onUpload: (file: File) => Promise; onDraft: (params: Record) => void; }) { const [models, setModels] = useState([]), [error, setError] = useState(""), [loading, setLoading] = useState(true), [sending, setSending] = useState(false); const [draft, setDraft] = useState>(() => ({ mode: "tts", text: "", voice: "Cherry", format: "wav", language: "Chinese", instrumental: true, gender: "female", rate: 1, pitch: 1, volume: 50, ...initial, })); const patch = (value: Record) => { const next = { ...draft, ...value }; setDraft(next); onDraft(next); }; useEffect(() => { const controller = new AbortController(); fetch("/api/canvas/audio/models", { signal: controller.signal }) .then(async (r) => { const data = await r.json(); if (!r.ok) throw Error(data.error || "音频模型读取失败"); setModels(data.models || []); }) .catch((e) => { if (!controller.signal.aborted) setError(e.message); }) .finally(() => { if (!controller.signal.aborted) setLoading(false); }); return () => controller.abort(); }, []); const options = models.filter((m) => m.kind === draft.mode), model = options.find((m) => m.id === draft.modelRef) || options[0], contract = model ? audioContract(model.modelId) : undefined; const voice = contract?.voices.some((v) => v.id === draft.voice) ? draft.voice : contract?.voices[0]?.id; const format = contract?.formats.includes(draft.format) ? draft.format : contract?.formats[0]; const input = "mt-1 w-full rounded-lg border border-white/15 bg-[#303030] p-2 text-sm"; const run = async () => { setError(""); setSending(true); try { if (!model) throw Error("当前模式没有已启用的官方模型"); const params = audioRequestSchema.parse({ ...draft, text: [draft.text, connectedText].filter(Boolean).join("\n\n"), modelRef: model.id, voice, format, ...(!contract?.controls ? { rate: 1, pitch: 1, volume: 50 } : {}), ...(!contract?.instructions ? { instructions: "" } : {}), }); buildAudioRequest(params, model.modelId); const savedDraft = { ...params, text: String(draft.text || "") }; patch(savedDraft); await onSubmit(params, savedDraft); } catch (e) { setError(e instanceof Error ? e.message : "提交失败"); } finally { setSending(false); } }; return (

语音与音乐 · 阿里云官方

{connectedText &&

已连接文本:{connectedText}

}