dsh-audiogen 0.2.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 +1101 -573
- package/lib/client.js.map +1 -1
- package/lib/index.js +528 -125
- package/package.json +1 -1
- package/skills/tts/SKILL.md +42 -4
- package/src/agent-audio-tools.ts +94 -7
- package/src/audio-engine.ts +215 -29
- package/src/audio-models.ts +132 -24
- package/src/audio-presets.ts +27 -33
- package/src/audio-store.ts +2 -0
- package/src/client/AudioGenPanel.tsx +140 -36
- package/src/client/SettingsCard.tsx +520 -176
- package/src/client/audio-panel.module.css +36 -0
- package/src/client/locales.ts +88 -18
- package/src/client/settings-card.module.css +424 -607
- package/src/protocol.ts +44 -2
- package/src/routes.ts +49 -4
package/src/protocol.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
export const AUDIOGEN_SETTINGS_NAMESPACE = 'dsh-audiogen'
|
|
9
9
|
|
|
10
10
|
/** Published package version shared by the host updater and the client UI. */
|
|
11
|
-
export const PLUGIN_VERSION = '0.2
|
|
11
|
+
export const PLUGIN_VERSION = '0.3.2'
|
|
12
12
|
|
|
13
13
|
/** Same-origin route family (loopback-only, mirroring dsh-imagegen). */
|
|
14
14
|
export const SETTINGS_API = {
|
|
@@ -45,7 +45,7 @@ export const HISTORY_API = {
|
|
|
45
45
|
export const HISTORY_MAX = 50
|
|
46
46
|
|
|
47
47
|
/** Audio generation modes. */
|
|
48
|
-
export type AudioMode = 'tts' | 'music' | 'sfx'
|
|
48
|
+
export type AudioMode = 'tts' | 'music' | 'sfx' | 'voice_design'
|
|
49
49
|
|
|
50
50
|
/** The capability category of an audio model/voice. */
|
|
51
51
|
export type AudioModelCategory =
|
|
@@ -94,6 +94,8 @@ export interface PresetProviderView {
|
|
|
94
94
|
name: string
|
|
95
95
|
apiUrl: string
|
|
96
96
|
hint: string
|
|
97
|
+
/** Official vendor website, shown as a link in the channel editor. */
|
|
98
|
+
site?: string
|
|
97
99
|
models: ModelMapping[]
|
|
98
100
|
}
|
|
99
101
|
|
|
@@ -106,6 +108,8 @@ export interface GenerateAudioRequest {
|
|
|
106
108
|
prompt: string
|
|
107
109
|
/** Optional voice alias for TTS. */
|
|
108
110
|
voice?: string
|
|
111
|
+
/** Optional preview text for voice-design APIs. */
|
|
112
|
+
previewText?: string
|
|
109
113
|
/** Optional speaking rate / speed multiplier. */
|
|
110
114
|
speed?: number
|
|
111
115
|
/** Requested duration in seconds (music/sfx). */
|
|
@@ -118,6 +122,38 @@ export interface GenerateAudioRequest {
|
|
|
118
122
|
channel?: string
|
|
119
123
|
/** Upstream model id actually sent (host-filled from alias mapping). */
|
|
120
124
|
upstream?: string
|
|
125
|
+
|
|
126
|
+
// ---- MiniMax TTS 专属字段(其他厂商渠道忽略)——————
|
|
127
|
+
/** MiniMax 音色情绪,如 happy/sad/angry/nervous/fearful/bored;默认按音色自身。 */
|
|
128
|
+
emotion?: string
|
|
129
|
+
/** MiniMax 音量,范围 0-10,默认 1。 */
|
|
130
|
+
vol?: number
|
|
131
|
+
/** MiniMax 音调偏移(半音),范围 -12~12,默认 0。 */
|
|
132
|
+
pitch?: number
|
|
133
|
+
/** MiniMax 文本归一化处理开关(默认 true)。 */
|
|
134
|
+
textNormalization?: boolean
|
|
135
|
+
/** MiniMax 数学公式朗读开关(默认 false)。 */
|
|
136
|
+
latexRead?: boolean
|
|
137
|
+
/** MiniMax 发音词典 tone 条目,元素形如 "处理/(chu3)(li3)" 或 "危险/dangerous"。 */
|
|
138
|
+
pronunciationTone?: string[]
|
|
139
|
+
/** MiniMax 采样率:16000/24000/32000/44100/48000,默认 32000。 */
|
|
140
|
+
sampleRate?: number
|
|
141
|
+
/** MiniMax 码率(bps):64000-320000,默认 128000。 */
|
|
142
|
+
bitrate?: number
|
|
143
|
+
/** MiniMax 声道数:1 或 2,默认 1。 */
|
|
144
|
+
audioChannel?: number
|
|
145
|
+
/** MiniMax 强制 CBR 编码(avoid VBR),默认 false。 */
|
|
146
|
+
forceCbr?: boolean
|
|
147
|
+
/** MiniMax 字幕开关:true 时响应携带字幕内容/文件。 */
|
|
148
|
+
subtitleEnable?: boolean
|
|
149
|
+
/** MiniMax AIGC 水印开关。 */
|
|
150
|
+
aigcWatermark?: boolean
|
|
151
|
+
/** MiniMax 语言增强(language_boost),如"中英混读",按模型支持情况。 */
|
|
152
|
+
languageBoost?: string
|
|
153
|
+
/** MiniMax 变声参数(voice_modify,speech-2.8 等支持)。 */
|
|
154
|
+
voiceModify?: { pitch?: number; intensity?: number; timbre?: number; soundEffects?: string }
|
|
155
|
+
/** MiniMax 双音色混合权重(timbre_weights)。 */
|
|
156
|
+
timbreWeights?: Array<{ voiceId: string; weight: number }>
|
|
121
157
|
}
|
|
122
158
|
|
|
123
159
|
/** One generated audio, normalized host-side to base64. */
|
|
@@ -134,6 +170,8 @@ export interface GeneratedAudio {
|
|
|
134
170
|
url: string
|
|
135
171
|
/** Stable audio id / file name. */
|
|
136
172
|
id: string
|
|
173
|
+
/** Optional voice id returned by a voice-design API. */
|
|
174
|
+
voiceId?: string
|
|
137
175
|
}
|
|
138
176
|
|
|
139
177
|
/** Successful generate outcome. */
|
|
@@ -153,6 +191,8 @@ export interface HistoryAudioRef {
|
|
|
153
191
|
mime: string
|
|
154
192
|
/** Duration in seconds when known. */
|
|
155
193
|
duration?: number
|
|
194
|
+
/** Optional generated voice id. */
|
|
195
|
+
voiceId?: string
|
|
156
196
|
}
|
|
157
197
|
|
|
158
198
|
/** A saved generation as the browser consumes it. */
|
|
@@ -163,6 +203,7 @@ export interface HistoryEntry {
|
|
|
163
203
|
model: string
|
|
164
204
|
prompt: string
|
|
165
205
|
voice?: string
|
|
206
|
+
voiceId?: string
|
|
166
207
|
speed?: number
|
|
167
208
|
duration?: number
|
|
168
209
|
format?: string
|
|
@@ -179,6 +220,7 @@ export interface HistoryEntryInput {
|
|
|
179
220
|
model: string
|
|
180
221
|
prompt: string
|
|
181
222
|
voice?: string
|
|
223
|
+
voiceId?: string
|
|
182
224
|
speed?: number
|
|
183
225
|
duration?: number
|
|
184
226
|
format?: string
|
package/src/routes.ts
CHANGED
|
@@ -90,18 +90,56 @@ function messageOf(error: unknown): string {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
function parseGenerateRequest(body: Record<string, unknown>): GenerateAudioRequest | undefined {
|
|
93
|
-
const mode = body.mode === 'music' ? 'music' : body.mode === 'sfx' ? 'sfx' : 'tts'
|
|
93
|
+
const mode = body.mode === 'music' ? 'music' : body.mode === 'sfx' ? 'sfx' : body.mode === 'voice_design' ? 'voice_design' : 'tts'
|
|
94
94
|
const prompt = typeof body.prompt === 'string' ? body.prompt.trim() : ''
|
|
95
95
|
if (prompt === '') return undefined
|
|
96
|
+
const num = (value: unknown): number | undefined => typeof value === 'number' && Number.isFinite(value) ? value : undefined
|
|
97
|
+
const str = (value: unknown): string | undefined => typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined
|
|
98
|
+
const flag = (value: unknown): boolean | undefined => typeof value === 'boolean' ? value : undefined
|
|
99
|
+
const tone = Array.isArray(body.pronunciationTone)
|
|
100
|
+
? body.pronunciationTone.filter((item): item is string => typeof item === 'string' && item.trim() !== '').map(item => item.trim())
|
|
101
|
+
: undefined
|
|
102
|
+
const voiceModifyRaw = body.voiceModify
|
|
103
|
+
const voiceModify = typeof voiceModifyRaw === 'object' && voiceModifyRaw !== null
|
|
104
|
+
? {
|
|
105
|
+
...(num((voiceModifyRaw as Record<string, unknown>).pitch) !== undefined ? { pitch: num((voiceModifyRaw as Record<string, unknown>).pitch)! } : {}),
|
|
106
|
+
...(num((voiceModifyRaw as Record<string, unknown>).intensity) !== undefined ? { intensity: num((voiceModifyRaw as Record<string, unknown>).intensity)! } : {}),
|
|
107
|
+
...(num((voiceModifyRaw as Record<string, unknown>).timbre) !== undefined ? { timbre: num((voiceModifyRaw as Record<string, unknown>).timbre)! } : {}),
|
|
108
|
+
...(str((voiceModifyRaw as Record<string, unknown>).soundEffects) !== undefined ? { soundEffects: str((voiceModifyRaw as Record<string, unknown>).soundEffects)! } : {}),
|
|
109
|
+
}
|
|
110
|
+
: undefined
|
|
111
|
+
const timbreWeights = Array.isArray(body.timbreWeights)
|
|
112
|
+
? body.timbreWeights
|
|
113
|
+
.filter((item): item is Record<string, unknown> => typeof item === 'object' && item !== null && typeof (item as Record<string, unknown>).voiceId === 'string' && typeof (item as Record<string, unknown>).weight === 'number')
|
|
114
|
+
.map(item => ({ voiceId: (item.voiceId as string).trim(), weight: item.weight as number }))
|
|
115
|
+
.filter(item => item.voiceId !== '')
|
|
116
|
+
: undefined
|
|
96
117
|
return {
|
|
97
118
|
mode,
|
|
98
119
|
model: typeof body.model === 'string' ? body.model.trim() : '',
|
|
99
120
|
prompt,
|
|
100
121
|
...(typeof body.voice === 'string' && body.voice.trim() !== '' ? { voice: body.voice.trim() } : {}),
|
|
101
|
-
...(typeof body.
|
|
102
|
-
...(
|
|
122
|
+
...(typeof body.previewText === 'string' && body.previewText.trim() !== '' ? { previewText: body.previewText.trim() } : {}),
|
|
123
|
+
...(num(body.speed) !== undefined ? { speed: num(body.speed)! } : {}),
|
|
124
|
+
...(num(body.duration) !== undefined ? { duration: num(body.duration)! } : {}),
|
|
103
125
|
...(typeof body.format === 'string' && body.format.trim() !== '' ? { format: body.format.trim() } : {}),
|
|
104
126
|
...(typeof body.channelId === 'string' && body.channelId !== '' ? { channelId: body.channelId } : {}),
|
|
127
|
+
// ---- MiniMax TTS 专属字段(其他厂商忽略) ----
|
|
128
|
+
...(str(body.emotion) !== undefined ? { emotion: str(body.emotion)! } : {}),
|
|
129
|
+
...(num(body.vol) !== undefined ? { vol: num(body.vol)! } : {}),
|
|
130
|
+
...(num(body.pitch) !== undefined ? { pitch: num(body.pitch)! } : {}),
|
|
131
|
+
...(flag(body.textNormalization) !== undefined ? { textNormalization: flag(body.textNormalization)! } : {}),
|
|
132
|
+
...(flag(body.latexRead) !== undefined ? { latexRead: flag(body.latexRead)! } : {}),
|
|
133
|
+
...(tone !== undefined && tone.length > 0 ? { pronunciationTone: tone } : {}),
|
|
134
|
+
...(num(body.sampleRate) !== undefined ? { sampleRate: num(body.sampleRate)! } : {}),
|
|
135
|
+
...(num(body.bitrate) !== undefined ? { bitrate: num(body.bitrate)! } : {}),
|
|
136
|
+
...(num(body.audioChannel) !== undefined ? { audioChannel: num(body.audioChannel)! } : {}),
|
|
137
|
+
...(flag(body.forceCbr) !== undefined ? { forceCbr: flag(body.forceCbr)! } : {}),
|
|
138
|
+
...(flag(body.subtitleEnable) !== undefined ? { subtitleEnable: flag(body.subtitleEnable)! } : {}),
|
|
139
|
+
...(flag(body.aigcWatermark) !== undefined ? { aigcWatermark: flag(body.aigcWatermark)! } : {}),
|
|
140
|
+
...(str(body.languageBoost) !== undefined ? { languageBoost: str(body.languageBoost)! } : {}),
|
|
141
|
+
...(voiceModify !== undefined ? { voiceModify } : {}),
|
|
142
|
+
...(timbreWeights !== undefined && timbreWeights.length > 0 ? { timbreWeights } : {}),
|
|
105
143
|
}
|
|
106
144
|
}
|
|
107
145
|
|
|
@@ -141,6 +179,10 @@ function resolveChannelRequest(
|
|
|
141
179
|
const target = explicit ?? defaults
|
|
142
180
|
const asked = request.model.trim()
|
|
143
181
|
if (asked === '') {
|
|
182
|
+
if (request.mode === 'voice_design') {
|
|
183
|
+
if (target === undefined) return { ok: false, code: 'no-channels', message: '尚未配置任何渠道' }
|
|
184
|
+
return { ok: true, request: { ...request, channelId: target.id, channel: target.name } }
|
|
185
|
+
}
|
|
144
186
|
const alias = target?.models[0]?.alias ?? ''
|
|
145
187
|
if (alias === '') {
|
|
146
188
|
return { ok: false, code: 'no-models', message: `渠道「${target?.name ?? ''}」尚未配置模型/音色,请先在设置中添加` }
|
|
@@ -207,7 +249,9 @@ export function makeRoutes(deps: AudiogenRoutesDeps): WebRoute[] {
|
|
|
207
249
|
?? view.channels[0]
|
|
208
250
|
const channel: AudioChannel = {
|
|
209
251
|
id: stored?.id ?? 'preview',
|
|
210
|
-
|
|
252
|
+
// The draft's own preset wins (even when empty = custom): discovery
|
|
253
|
+
// must follow the vendor being configured, not the default channel.
|
|
254
|
+
preset: typeof body?.preset === 'string' ? body.preset.trim() : (stored?.preset ?? ''),
|
|
211
255
|
name: stored?.name ?? '',
|
|
212
256
|
apiUrl: typeof body?.apiUrl === 'string' && body.apiUrl.trim() !== '' ? body.apiUrl.trim() : (stored?.apiUrl ?? ''),
|
|
213
257
|
apiKey: typeof body?.apiKey === 'string' && body.apiKey.trim() !== '' ? body.apiKey.trim() : (stored?.apiKey ?? ''),
|
|
@@ -300,6 +344,7 @@ export function makeRoutes(deps: AudiogenRoutesDeps): WebRoute[] {
|
|
|
300
344
|
mime: saved.mime,
|
|
301
345
|
bytes: saved.bytes,
|
|
302
346
|
url: `${AUDIO_API.file}/${encodeURIComponent(saved.file)}`,
|
|
347
|
+
...(output.voiceId === undefined ? {} : { voiceId: output.voiceId }),
|
|
303
348
|
})
|
|
304
349
|
}
|
|
305
350
|
let history
|