dsh-audiogen 0.4.8 → 0.4.10
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 +438 -352
- package/lib/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/studio-view.tsx +67 -11
- package/src/protocol.ts +2 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-audiogen",
|
|
3
3
|
"description": "AI audio generation plugin for the dsh web GUI: multi-vendor TTS/music/sound-effect channels (OpenAI-compatible, ElevenLabs, MiniMax, Stability AI and custom), per-channel model/voice catalogs, Agent tool and a sidebar AI 音频 panel.",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.10",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -14,7 +14,7 @@ import { globalFieldSpecs, overrideRowSpecs, presetLabel, type FieldSpec } from
|
|
|
14
14
|
import { tt } from './helpers.ts'
|
|
15
15
|
import {
|
|
16
16
|
HISTORY_API,
|
|
17
|
-
type AudioMode, type GeneratedAudio, type GenerateAudioRequest, type HistoryEntry, type LibraryEntry,
|
|
17
|
+
type AudioMode, type GeneratedAudio, type GenerateAudioRequest, type HistoryAudioRef, type HistoryEntry, type LibraryEntry,
|
|
18
18
|
} from '../protocol.ts'
|
|
19
19
|
import type { ModelOption } from './settings-scope.ts'
|
|
20
20
|
import { AudioPlayer } from './audio-player.tsx'
|
|
@@ -161,6 +161,11 @@ function dataUrlOf(audio: GeneratedAudio): string {
|
|
|
161
161
|
return `data:${audio.mime};base64,${audio.b64}`
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
/** 结果卡片音频源:优先同源 URL(历史恢复的音频只有 URL 没有 b64),无 URL 再回退 data URL。 */
|
|
165
|
+
function srcOf(audio: GeneratedAudio): string {
|
|
166
|
+
return audio.url !== '' ? audio.url : dataUrlOf(audio)
|
|
167
|
+
}
|
|
168
|
+
|
|
164
169
|
function fileNameOf(url: string): string {
|
|
165
170
|
try {
|
|
166
171
|
return decodeURIComponent(new URL(url, 'http://localhost').pathname.split('/').pop() ?? '')
|
|
@@ -273,8 +278,23 @@ export function StudioView(props: {
|
|
|
273
278
|
// 历史记录分类 tab
|
|
274
279
|
const [historyTab, setHistoryTab] = useState<HistoryTab>('all')
|
|
275
280
|
// 模型对比
|
|
276
|
-
|
|
277
|
-
const [
|
|
281
|
+
/** 对比模式与对比模型选择按模式独立保存:恢复对比任务或切换模式时不会串到其他模块。 */
|
|
282
|
+
const [compareModeByMode, setCompareModeByMode] = useState<Record<AudioMode, boolean>>({ tts: false, music: false, sfx: false, voice_design: false })
|
|
283
|
+
const compareMode = compareModeByMode[mode] ?? false
|
|
284
|
+
const setCompareMode = (next: boolean): void => {
|
|
285
|
+
const target = mode
|
|
286
|
+
setCompareModeByMode(current => (current[target] === next ? current : { ...current, [target]: next }))
|
|
287
|
+
}
|
|
288
|
+
const [compareModelsByMode, setCompareModelsByMode] = useState<Record<AudioMode, string[]>>({ tts: [], music: [], sfx: [], voice_design: [] })
|
|
289
|
+
const compareModels = compareModelsByMode[mode] ?? []
|
|
290
|
+
const setCompareModels = (next: string[] | ((current: string[]) => string[])): void => {
|
|
291
|
+
const target = mode
|
|
292
|
+
setCompareModelsByMode(current => {
|
|
293
|
+
const previous = current[target] ?? []
|
|
294
|
+
const updated = typeof next === 'function' ? next(previous) : next
|
|
295
|
+
return updated === previous ? current : { ...current, [target]: updated }
|
|
296
|
+
})
|
|
297
|
+
}
|
|
278
298
|
// 每模型参数覆盖(留空 = 自动沿用全局)
|
|
279
299
|
const [overrides, setOverrides] = useState<Record<string, Record<string, string>>>({})
|
|
280
300
|
const { entries, reload, clear } = useHistory()
|
|
@@ -534,8 +554,9 @@ export function StudioView(props: {
|
|
|
534
554
|
}
|
|
535
555
|
|
|
536
556
|
/** 从历史参数回填表单(参考 AI 生图「恢复」):配置 + prompt 一键复用。
|
|
537
|
-
* compareModelsRestore:对比任务恢复为对比模式;overridesRestore 由各模型 params
|
|
538
|
-
|
|
557
|
+
* compareModelsRestore:对比任务恢复为对比模式;overridesRestore 由各模型 params 差异重建;
|
|
558
|
+
* restoredGroups:把历史音频以「已完成任务」形式放回结果列,可直接试听/下载而无需重新生成。 */
|
|
559
|
+
const restoreFromParams = (params: Record<string, unknown>, modeValue: AudioMode, singleModel: string, compareModelsRestore?: string[], overridesRestore?: Record<string, Record<string, string>>, restoredGroups?: Array<{ model: string; audio: GeneratedAudio[] }>): void => {
|
|
539
560
|
const str = (key: string): string | undefined => {
|
|
540
561
|
const v = params[key]
|
|
541
562
|
return typeof v === 'string' && v.trim() !== '' ? v.trim() : undefined
|
|
@@ -558,11 +579,12 @@ export function StudioView(props: {
|
|
|
558
579
|
}
|
|
559
580
|
const modelValue = str('model') ?? singleModel
|
|
560
581
|
if (modelValue !== '') setModel(modelValue)
|
|
582
|
+
// 直接写入被恢复模式自己的槽位(此闭包里 mode 仍是旧值)。
|
|
561
583
|
if (compareModelsRestore !== undefined && compareModelsRestore.length > 0) {
|
|
562
|
-
|
|
563
|
-
|
|
584
|
+
setCompareModeByMode(current => ({ ...current, [modeValue]: true }))
|
|
585
|
+
setCompareModelsByMode(current => ({ ...current, [modeValue]: compareModelsRestore }))
|
|
564
586
|
} else {
|
|
565
|
-
|
|
587
|
+
setCompareModeByMode(current => ({ ...current, [modeValue]: false }))
|
|
566
588
|
}
|
|
567
589
|
const voiceValue = str('voice')
|
|
568
590
|
if (voiceValue !== undefined) setVoice(voiceValue)
|
|
@@ -609,9 +631,39 @@ export function StudioView(props: {
|
|
|
609
631
|
if (channelIdValue !== undefined && modeValue === 'voice_design') setDesignChannelId(channelIdValue)
|
|
610
632
|
// 恢复对比任务时重建每模型参数覆盖(各模型 params 与第一项的差异);单条恢复清空覆盖。
|
|
611
633
|
setOverrides(overridesRestore ?? {})
|
|
612
|
-
|
|
634
|
+
// 把历史音频以「已完成任务」放回结果列(不重新生成,直接用同源 URL 试听/下载)。
|
|
635
|
+
if (restoredGroups !== undefined && restoredGroups.length > 0) {
|
|
636
|
+
const now = Date.now()
|
|
637
|
+
const total = restoredGroups.reduce((sum, group) => sum + group.audio.length, 0)
|
|
638
|
+
const restoredTask: StudioTask = {
|
|
639
|
+
id: `t-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
640
|
+
mode: modeValue,
|
|
641
|
+
kind: restoredGroups.length > 1 ? 'compare' : 'single',
|
|
642
|
+
prompt: promptValue ?? '',
|
|
643
|
+
label: restoredGroups.length > 1 ? `对比 ${restoredGroups.map(group => group.model).join(' / ')}` : restoredGroups[0]!.model,
|
|
644
|
+
status: 'done',
|
|
645
|
+
progress: { done: total, total, current: '' },
|
|
646
|
+
startedAt: now,
|
|
647
|
+
finishedAt: now,
|
|
648
|
+
groups: restoredGroups.map(group => ({ model: group.model, state: 'done' as const, outputs: group.audio })),
|
|
649
|
+
}
|
|
650
|
+
setTasks(current => [restoredTask, ...current])
|
|
651
|
+
}
|
|
652
|
+
props.showToast(restoredGroups !== undefined && restoredGroups.length > 0 ? '已恢复配置与音频(历史结果已放回中间栏)' : '已恢复该次生成的配置,可直接再次生成')
|
|
613
653
|
}
|
|
614
654
|
|
|
655
|
+
/** 历史音频引用 → 结果列卡片可用的音频对象(复用同源 URL,不重新生成)。 */
|
|
656
|
+
const historyAudioToGenerated = (entryId: string, refs: HistoryAudioRef[]): GeneratedAudio[] =>
|
|
657
|
+
refs.map((ref, index) => ({
|
|
658
|
+
id: `${entryId}-${index}`,
|
|
659
|
+
file: '',
|
|
660
|
+
b64: '',
|
|
661
|
+
mime: ref.mime,
|
|
662
|
+
bytes: 0,
|
|
663
|
+
url: ref.url,
|
|
664
|
+
...(ref.voiceId === undefined ? {} : { voiceId: ref.voiceId }),
|
|
665
|
+
}))
|
|
666
|
+
|
|
615
667
|
/** 由对比历史条目重建每模型参数覆盖:以第一项为全局基准,其余条目与基准不同的字段即覆盖。 */
|
|
616
668
|
const overridesOfCompare = (models: Array<{ model: string; entry: HistoryEntry }>): Record<string, Record<string, string>> => {
|
|
617
669
|
const base = (models[0]?.entry.params ?? {}) as Record<string, unknown>
|
|
@@ -833,9 +885,9 @@ export function StudioView(props: {
|
|
|
833
885
|
) : null}
|
|
834
886
|
<span className={css.audioCardIndex}>#{index + 1}</span>
|
|
835
887
|
</div>
|
|
836
|
-
<AudioPlayer src={
|
|
888
|
+
<AudioPlayer src={srcOf(audio)} itemKey={`${label}-${audio.id}`} />
|
|
837
889
|
<div className={css.audioCardActions}>
|
|
838
|
-
<a className={css.ghostButton} href={
|
|
890
|
+
<a className={css.ghostButton} href={srcOf(audio)} download={`generated-${index + 1}.${audio.mime.split('/')[1]?.replace('mpeg', 'mp3') ?? 'mp3'}`}>
|
|
839
891
|
<DownloadIcon /> 下载
|
|
840
892
|
</a>
|
|
841
893
|
{saved ? (
|
|
@@ -1257,6 +1309,7 @@ export function StudioView(props: {
|
|
|
1257
1309
|
item.models[0]?.model ?? '',
|
|
1258
1310
|
item.models.map(model => model.model),
|
|
1259
1311
|
overridesOfCompare(item.models),
|
|
1312
|
+
item.models.map(model => ({ model: model.model, audio: historyAudioToGenerated(model.entry.id, model.entry.audio) })),
|
|
1260
1313
|
)}>↺</button>
|
|
1261
1314
|
<button type="button" className={css.historyIcon} title="删除整个对比任务" onClick={() => void deleteHistoryEntries(item.models.map(model => model.entry.id))}>✕</button>
|
|
1262
1315
|
</div>
|
|
@@ -1277,6 +1330,9 @@ export function StudioView(props: {
|
|
|
1277
1330
|
(entry.params ?? {}) as Record<string, unknown>,
|
|
1278
1331
|
entry.mode,
|
|
1279
1332
|
entry.model,
|
|
1333
|
+
undefined,
|
|
1334
|
+
undefined,
|
|
1335
|
+
[{ model: entry.model, audio: historyAudioToGenerated(entry.id, entry.audio) }],
|
|
1280
1336
|
)}>↺</button>
|
|
1281
1337
|
<button type="button" className={css.historyIcon} title="删除这条记录" onClick={() => void deleteHistoryEntries([entry.id])}>✕</button>
|
|
1282
1338
|
<button type="button" className={css.historyIcon} title="加入资源库" onClick={() => openSaveDialog(audioRefsOfEntry(entry), contextOfEntry(entry))}>
|
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.4.
|
|
11
|
+
export const PLUGIN_VERSION = '0.4.10'
|
|
12
12
|
|
|
13
13
|
/** Same-origin route family (loopback-only, mirroring dsh-imagegen). */
|
|
14
14
|
export const SETTINGS_API = {
|
|
@@ -163,7 +163,7 @@ export interface GenerateAudioRequest {
|
|
|
163
163
|
loop?: boolean
|
|
164
164
|
/** ElevenLabs 音效:提示词影响度 0-1(prompt_influence,默认 0.3)。 */
|
|
165
165
|
promptInfluence?: number
|
|
166
|
-
/** Stable Audio 随机种子(seed,0
|
|
166
|
+
/** Stable Audio 随机种子(seed,0.4.104967294,默认 0=随机)。 */
|
|
167
167
|
seed?: number
|
|
168
168
|
/** Stable Audio 采样步数(steps,按模型收敛:stable-audio-2: 30-100;2.5/3: 4-8)。 */
|
|
169
169
|
steps?: number
|