dsh-audiogen 0.4.8 → 0.4.9
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 +400 -354
- package/lib/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/studio-view.tsx +46 -6
- package/src/protocol.ts +1 -1
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.9",
|
|
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() ?? '')
|
|
@@ -534,8 +539,9 @@ export function StudioView(props: {
|
|
|
534
539
|
}
|
|
535
540
|
|
|
536
541
|
/** 从历史参数回填表单(参考 AI 生图「恢复」):配置 + prompt 一键复用。
|
|
537
|
-
* compareModelsRestore:对比任务恢复为对比模式;overridesRestore 由各模型 params
|
|
538
|
-
|
|
542
|
+
* compareModelsRestore:对比任务恢复为对比模式;overridesRestore 由各模型 params 差异重建;
|
|
543
|
+
* restoredGroups:把历史音频以「已完成任务」形式放回结果列,可直接试听/下载而无需重新生成。 */
|
|
544
|
+
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
545
|
const str = (key: string): string | undefined => {
|
|
540
546
|
const v = params[key]
|
|
541
547
|
return typeof v === 'string' && v.trim() !== '' ? v.trim() : undefined
|
|
@@ -609,9 +615,39 @@ export function StudioView(props: {
|
|
|
609
615
|
if (channelIdValue !== undefined && modeValue === 'voice_design') setDesignChannelId(channelIdValue)
|
|
610
616
|
// 恢复对比任务时重建每模型参数覆盖(各模型 params 与第一项的差异);单条恢复清空覆盖。
|
|
611
617
|
setOverrides(overridesRestore ?? {})
|
|
612
|
-
|
|
618
|
+
// 把历史音频以「已完成任务」放回结果列(不重新生成,直接用同源 URL 试听/下载)。
|
|
619
|
+
if (restoredGroups !== undefined && restoredGroups.length > 0) {
|
|
620
|
+
const now = Date.now()
|
|
621
|
+
const total = restoredGroups.reduce((sum, group) => sum + group.audio.length, 0)
|
|
622
|
+
const restoredTask: StudioTask = {
|
|
623
|
+
id: `t-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
624
|
+
mode: modeValue,
|
|
625
|
+
kind: restoredGroups.length > 1 ? 'compare' : 'single',
|
|
626
|
+
prompt: promptValue ?? '',
|
|
627
|
+
label: restoredGroups.length > 1 ? `对比 ${restoredGroups.map(group => group.model).join(' / ')}` : restoredGroups[0]!.model,
|
|
628
|
+
status: 'done',
|
|
629
|
+
progress: { done: total, total, current: '' },
|
|
630
|
+
startedAt: now,
|
|
631
|
+
finishedAt: now,
|
|
632
|
+
groups: restoredGroups.map(group => ({ model: group.model, state: 'done' as const, outputs: group.audio })),
|
|
633
|
+
}
|
|
634
|
+
setTasks(current => [restoredTask, ...current])
|
|
635
|
+
}
|
|
636
|
+
props.showToast(restoredGroups !== undefined && restoredGroups.length > 0 ? '已恢复配置与音频(历史结果已放回中间栏)' : '已恢复该次生成的配置,可直接再次生成')
|
|
613
637
|
}
|
|
614
638
|
|
|
639
|
+
/** 历史音频引用 → 结果列卡片可用的音频对象(复用同源 URL,不重新生成)。 */
|
|
640
|
+
const historyAudioToGenerated = (entryId: string, refs: HistoryAudioRef[]): GeneratedAudio[] =>
|
|
641
|
+
refs.map((ref, index) => ({
|
|
642
|
+
id: `${entryId}-${index}`,
|
|
643
|
+
file: '',
|
|
644
|
+
b64: '',
|
|
645
|
+
mime: ref.mime,
|
|
646
|
+
bytes: 0,
|
|
647
|
+
url: ref.url,
|
|
648
|
+
...(ref.voiceId === undefined ? {} : { voiceId: ref.voiceId }),
|
|
649
|
+
}))
|
|
650
|
+
|
|
615
651
|
/** 由对比历史条目重建每模型参数覆盖:以第一项为全局基准,其余条目与基准不同的字段即覆盖。 */
|
|
616
652
|
const overridesOfCompare = (models: Array<{ model: string; entry: HistoryEntry }>): Record<string, Record<string, string>> => {
|
|
617
653
|
const base = (models[0]?.entry.params ?? {}) as Record<string, unknown>
|
|
@@ -833,9 +869,9 @@ export function StudioView(props: {
|
|
|
833
869
|
) : null}
|
|
834
870
|
<span className={css.audioCardIndex}>#{index + 1}</span>
|
|
835
871
|
</div>
|
|
836
|
-
<AudioPlayer src={
|
|
872
|
+
<AudioPlayer src={srcOf(audio)} itemKey={`${label}-${audio.id}`} />
|
|
837
873
|
<div className={css.audioCardActions}>
|
|
838
|
-
<a className={css.ghostButton} href={
|
|
874
|
+
<a className={css.ghostButton} href={srcOf(audio)} download={`generated-${index + 1}.${audio.mime.split('/')[1]?.replace('mpeg', 'mp3') ?? 'mp3'}`}>
|
|
839
875
|
<DownloadIcon /> 下载
|
|
840
876
|
</a>
|
|
841
877
|
{saved ? (
|
|
@@ -1257,6 +1293,7 @@ export function StudioView(props: {
|
|
|
1257
1293
|
item.models[0]?.model ?? '',
|
|
1258
1294
|
item.models.map(model => model.model),
|
|
1259
1295
|
overridesOfCompare(item.models),
|
|
1296
|
+
item.models.map(model => ({ model: model.model, audio: historyAudioToGenerated(model.entry.id, model.entry.audio) })),
|
|
1260
1297
|
)}>↺</button>
|
|
1261
1298
|
<button type="button" className={css.historyIcon} title="删除整个对比任务" onClick={() => void deleteHistoryEntries(item.models.map(model => model.entry.id))}>✕</button>
|
|
1262
1299
|
</div>
|
|
@@ -1277,6 +1314,9 @@ export function StudioView(props: {
|
|
|
1277
1314
|
(entry.params ?? {}) as Record<string, unknown>,
|
|
1278
1315
|
entry.mode,
|
|
1279
1316
|
entry.model,
|
|
1317
|
+
undefined,
|
|
1318
|
+
undefined,
|
|
1319
|
+
[{ model: entry.model, audio: historyAudioToGenerated(entry.id, entry.audio) }],
|
|
1280
1320
|
)}>↺</button>
|
|
1281
1321
|
<button type="button" className={css.historyIcon} title="删除这条记录" onClick={() => void deleteHistoryEntries([entry.id])}>✕</button>
|
|
1282
1322
|
<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.9'
|
|
12
12
|
|
|
13
13
|
/** Same-origin route family (loopback-only, mirroring dsh-imagegen). */
|
|
14
14
|
export const SETTINGS_API = {
|