dsh-audiogen 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +1046 -546
- package/lib/client.js.map +1 -1
- package/lib/index.js +445 -114
- package/package.json +1 -1
- package/skills/tts/SKILL.md +42 -4
- package/src/agent-audio-tools.ts +75 -2
- package/src/audio-engine.ts +165 -22
- package/src/audio-models.ts +132 -24
- package/src/audio-presets.ts +27 -33
- package/src/client/AudioGenPanel.tsx +69 -1
- package/src/client/SettingsCard.tsx +520 -176
- package/src/client/audio-panel.module.css +36 -0
- package/src/client/locales.ts +84 -16
- package/src/client/settings-card.module.css +424 -607
- package/src/protocol.ts +35 -1
- package/src/routes.ts +42 -3
package/src/audio-presets.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Built-in audio provider catalog (presets).
|
|
3
3
|
* Framework-free pure data; served to the settings card through a host route.
|
|
4
|
+
*
|
|
5
|
+
* Only the officially supported audio vendors are offered here — MiniMax,
|
|
6
|
+
* ElevenLabs and Stability AI. Any other endpoint (including OpenAI-compatible
|
|
7
|
+
* TTS gateways) is added through the "自定义渠道" flow instead.
|
|
4
8
|
*/
|
|
5
9
|
|
|
6
10
|
import type { ModelMapping } from './protocol.ts'
|
|
@@ -15,39 +19,19 @@ export interface AudioPresetProvider {
|
|
|
15
19
|
apiUrl: string
|
|
16
20
|
/** One-line description shown in the picker. */
|
|
17
21
|
hint: string
|
|
22
|
+
/** Official vendor website, shown as a link in the channel editor. */
|
|
23
|
+
site?: string
|
|
18
24
|
/** Known model/voice catalog prefilled into the channel. */
|
|
19
25
|
models: ModelMapping[]
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
export const AUDIO_PRESETS: AudioPresetProvider[] = [
|
|
23
|
-
{
|
|
24
|
-
id: 'openai-tts',
|
|
25
|
-
name: 'OpenAI · TTS',
|
|
26
|
-
apiUrl: 'https://api.openai.com/v1',
|
|
27
|
-
hint: 'OpenAI 官方语音合成接口(/audio/speech)',
|
|
28
|
-
models: [
|
|
29
|
-
{ alias: 'tts-1', id: 'tts-1', category: 'tts' },
|
|
30
|
-
{ alias: 'tts-1-hd', id: 'tts-1-hd', category: 'tts' },
|
|
31
|
-
{ alias: 'gpt-4o-mini-tts', id: 'gpt-4o-mini-tts', category: 'tts' },
|
|
32
|
-
],
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
id: 'elevenlabs',
|
|
36
|
-
name: 'ElevenLabs',
|
|
37
|
-
apiUrl: 'https://api.elevenlabs.io/v1',
|
|
38
|
-
hint: 'ElevenLabs TTS;模型列表请填写你的 Voice ID(如 Rachel / Adam 等别名)',
|
|
39
|
-
models: [
|
|
40
|
-
{ alias: 'Rachel', id: '21m00Tcm4TlvDq8ikWAM', category: 'tts' },
|
|
41
|
-
{ alias: 'Adam', id: 'pNInz6obpgDQGcFmaJgB', category: 'tts' },
|
|
42
|
-
{ alias: 'Antoni', id: 'ErXwobaYiN019PkySvjV', category: 'tts' },
|
|
43
|
-
{ alias: 'Bella', id: 'EXAVITQu4vr4xnSDxMaL', category: 'tts' },
|
|
44
|
-
],
|
|
45
|
-
},
|
|
46
29
|
{
|
|
47
30
|
id: 'minimax',
|
|
48
31
|
name: 'MiniMax',
|
|
49
32
|
apiUrl: 'https://api.minimaxi.com',
|
|
50
|
-
|
|
33
|
+
site: 'https://www.minimaxi.com',
|
|
34
|
+
hint: 'MiniMax 官方音频:音色设计 / TTS / 音乐生成;建议点击「获取可用模型」拉取账号音色与模型',
|
|
51
35
|
models: [
|
|
52
36
|
// TTS models
|
|
53
37
|
{ alias: 'speech-2.8-hd', id: 'speech-2.8-hd', category: 'tts' },
|
|
@@ -64,23 +48,33 @@ export const AUDIO_PRESETS: AudioPresetProvider[] = [
|
|
|
64
48
|
{ alias: 'music-cover', id: 'music-cover', category: 'music' },
|
|
65
49
|
],
|
|
66
50
|
},
|
|
51
|
+
{
|
|
52
|
+
id: 'elevenlabs',
|
|
53
|
+
name: 'ElevenLabs',
|
|
54
|
+
apiUrl: 'https://api.elevenlabs.io/v1',
|
|
55
|
+
site: 'https://elevenlabsai.cn',
|
|
56
|
+
hint: 'ElevenLabs 语音合成(TTS);建议点击「获取可用模型」拉取音色与模型',
|
|
57
|
+
models: [
|
|
58
|
+
{ alias: 'Rachel', id: '21m00Tcm4TlvDq8ikWAM', category: 'tts' },
|
|
59
|
+
{ alias: 'Adam', id: 'pNInz6obpgDQGcFmaJgB', category: 'tts' },
|
|
60
|
+
{ alias: 'Antoni', id: 'ErXwobaYiN019PkySvjV', category: 'tts' },
|
|
61
|
+
{ alias: 'Bella', id: 'EXAVITQu4vr4xnSDxMaL', category: 'tts' },
|
|
62
|
+
{ alias: 'eleven_multilingual_v2', id: 'eleven_multilingual_v2', category: 'tts' },
|
|
63
|
+
{ alias: 'eleven_turbo_v2_5', id: 'eleven_turbo_v2_5', category: 'tts' },
|
|
64
|
+
{ alias: 'eleven_flash_v2_5', id: 'eleven_flash_v2_5', category: 'tts' },
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
67
|
{
|
|
68
68
|
id: 'stability-audio',
|
|
69
|
-
name: 'Stability AI
|
|
69
|
+
name: 'Stability AI(stable-audio)',
|
|
70
70
|
apiUrl: 'https://api.stability.ai/v2beta/audio',
|
|
71
|
-
|
|
71
|
+
site: 'https://stability.ai/stable-audio',
|
|
72
|
+
hint: 'Stability AI 音乐 / 音效生成(stable-audio 系列)',
|
|
72
73
|
models: [
|
|
73
74
|
{ alias: 'stable-audio-2.0', id: 'stable-audio-2.0', category: 'music' },
|
|
74
75
|
{ alias: 'stable-audio-1.0', id: 'stable-audio-1.0', category: 'music' },
|
|
75
76
|
],
|
|
76
77
|
},
|
|
77
|
-
{
|
|
78
|
-
id: 'custom',
|
|
79
|
-
name: '自定义渠道',
|
|
80
|
-
apiUrl: '',
|
|
81
|
-
hint: '任意兼容接口;支持 OpenAI 兼容 TTS,或返回音频字节 / JSON 的通用 POST',
|
|
82
|
-
models: [],
|
|
83
|
-
},
|
|
84
78
|
]
|
|
85
79
|
|
|
86
80
|
/** Look up one built-in provider by id. */
|
|
@@ -61,11 +61,25 @@ export function AudioGenPanel(props: { api: AudiogenApi; scope: AudiogenScope })
|
|
|
61
61
|
const [speed, setSpeed] = useState('')
|
|
62
62
|
const [duration, setDuration] = useState('')
|
|
63
63
|
const [format, setFormat] = useState('mp3')
|
|
64
|
+
// MiniMax TTS 高级参数(其他厂商忽略)
|
|
65
|
+
const [emotion, setEmotion] = useState('')
|
|
66
|
+
const [vol, setVol] = useState('')
|
|
67
|
+
const [pitch, setPitch] = useState('')
|
|
68
|
+
const [toneText, setToneText] = useState('')
|
|
69
|
+
const [sampleRate, setSampleRate] = useState('')
|
|
70
|
+
const [bitrate, setBitrate] = useState('')
|
|
71
|
+
const [audioChannel, setAudioChannel] = useState('')
|
|
72
|
+
const [subtitle, setSubtitle] = useState(false)
|
|
64
73
|
const [loading, setLoading] = useState(false)
|
|
65
74
|
const [error, setError] = useState<string | null>(null)
|
|
66
75
|
const [outputs, setOutputs] = useState<GeneratedAudio[]>([])
|
|
67
76
|
const { entries, reload, clear } = useHistory()
|
|
68
77
|
|
|
78
|
+
const isMiniMaxChannel = useMemo(() => {
|
|
79
|
+
const target = channels.find(candidate => candidate.id === modelOptions.defaultChannelId) ?? channels[0]
|
|
80
|
+
return target !== undefined && (target.preset === 'minimax' || /minimax/i.test(target.apiUrl))
|
|
81
|
+
}, [channels, modelOptions.defaultChannelId])
|
|
82
|
+
|
|
69
83
|
const visibleModels = useMemo(() => {
|
|
70
84
|
if (mode === 'voice_design') return []
|
|
71
85
|
return modelOptions.models
|
|
@@ -96,6 +110,14 @@ export function AudioGenPanel(props: { api: AudiogenApi; scope: AudiogenScope })
|
|
|
96
110
|
...(speed.trim() !== '' ? { speed: Number(speed) } : {}),
|
|
97
111
|
...(duration.trim() !== '' ? { duration: Number(duration) } : {}),
|
|
98
112
|
...(format.trim() !== '' ? { format: format.trim() } : {}),
|
|
113
|
+
...(emotion.trim() !== '' ? { emotion: emotion.trim() } : {}),
|
|
114
|
+
...(vol.trim() !== '' ? { vol: Number(vol) } : {}),
|
|
115
|
+
...(pitch.trim() !== '' ? { pitch: Number(pitch) } : {}),
|
|
116
|
+
...(toneText.trim() !== '' ? { pronunciationTone: toneText.split('\n').map(item => item.trim()).filter(item => item !== '') } : {}),
|
|
117
|
+
...(sampleRate.trim() !== '' ? { sampleRate: Number(sampleRate) } : {}),
|
|
118
|
+
...(bitrate.trim() !== '' ? { bitrate: Number(bitrate) } : {}),
|
|
119
|
+
...(audioChannel.trim() !== '' ? { audioChannel: Number(audioChannel) } : {}),
|
|
120
|
+
...(subtitle ? { subtitleEnable: true } : {}),
|
|
99
121
|
})
|
|
100
122
|
if (!response.ok) {
|
|
101
123
|
setError(response.message ?? '生成失败')
|
|
@@ -166,7 +188,7 @@ export function AudioGenPanel(props: { api: AudiogenApi; scope: AudiogenScope })
|
|
|
166
188
|
{mode === 'tts' ? (
|
|
167
189
|
<label className={css.label}>
|
|
168
190
|
<span>{tt('voice.label')}</span>
|
|
169
|
-
<input className={css.input} value={voice} onChange={event => setVoice(event.target.value)} placeholder=
|
|
191
|
+
<input className={css.input} value={voice} onChange={event => setVoice(event.target.value)} placeholder={isMiniMaxChannel ? 'male-qn-qingse / female-shaonv' : 'alloy / 自定义音色'} />
|
|
170
192
|
</label>
|
|
171
193
|
) : null}
|
|
172
194
|
|
|
@@ -177,6 +199,52 @@ export function AudioGenPanel(props: { api: AudiogenApi; scope: AudiogenScope })
|
|
|
177
199
|
</label>
|
|
178
200
|
) : null}
|
|
179
201
|
|
|
202
|
+
{mode === 'tts' && isMiniMaxChannel ? (
|
|
203
|
+
<details className={css.advanced}>
|
|
204
|
+
<summary>MiniMax 高级参数</summary>
|
|
205
|
+
<label className={css.label}>
|
|
206
|
+
<span>情绪 emotion</span>
|
|
207
|
+
<input className={css.input} value={emotion} onChange={event => setEmotion(event.target.value)} placeholder="happy / sad / angry / nervous…" />
|
|
208
|
+
</label>
|
|
209
|
+
<div className={css.row}>
|
|
210
|
+
<label className={css.label}>
|
|
211
|
+
<span>音量 vol (0-10)</span>
|
|
212
|
+
<input className={css.input} type="number" min="0" max="10" step="0.5" value={vol} onChange={event => setVol(event.target.value)} placeholder="1" />
|
|
213
|
+
</label>
|
|
214
|
+
<label className={css.label}>
|
|
215
|
+
<span>音调 pitch (-12~12)</span>
|
|
216
|
+
<input className={css.input} type="number" min="-12" max="12" value={pitch} onChange={event => setPitch(event.target.value)} placeholder="0" />
|
|
217
|
+
</label>
|
|
218
|
+
</div>
|
|
219
|
+
<div className={css.row}>
|
|
220
|
+
<label className={css.label}>
|
|
221
|
+
<span>采样率</span>
|
|
222
|
+
<input className={css.input} type="number" min="16000" max="48000" step="8000" value={sampleRate} onChange={event => setSampleRate(event.target.value)} placeholder="32000" />
|
|
223
|
+
</label>
|
|
224
|
+
<label className={css.label}>
|
|
225
|
+
<span>码率 bps</span>
|
|
226
|
+
<input className={css.input} type="number" min="64000" max="320000" step="8000" value={bitrate} onChange={event => setBitrate(event.target.value)} placeholder="128000" />
|
|
227
|
+
</label>
|
|
228
|
+
<label className={css.label}>
|
|
229
|
+
<span>声道</span>
|
|
230
|
+
<select className={css.select} value={audioChannel} onChange={event => setAudioChannel(event.target.value)}>
|
|
231
|
+
<option value="">默认(1)</option>
|
|
232
|
+
<option value="1">1</option>
|
|
233
|
+
<option value="2">2</option>
|
|
234
|
+
</select>
|
|
235
|
+
</label>
|
|
236
|
+
</div>
|
|
237
|
+
<label className={css.label}>
|
|
238
|
+
<span>发音词典(每行一条:"文字/读音")</span>
|
|
239
|
+
<textarea className={css.textarea} value={toneText} onChange={event => setToneText(event.target.value)} placeholder={'处理/(chu3)(li3)\n危险/dangerous'} />
|
|
240
|
+
</label>
|
|
241
|
+
<label className={css.checkbox}>
|
|
242
|
+
<input type="checkbox" checked={subtitle} onChange={event => setSubtitle(event.target.checked)} />
|
|
243
|
+
<span>生成字幕 subtitle_enable</span>
|
|
244
|
+
</label>
|
|
245
|
+
</details>
|
|
246
|
+
) : null}
|
|
247
|
+
|
|
180
248
|
{mode === 'music' || mode === 'sfx' ? (
|
|
181
249
|
<label className={css.label}>
|
|
182
250
|
<span>{tt('duration.label')}</span>
|