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/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.8",
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
- const restoreFromParams = (params: Record<string, unknown>, modeValue: AudioMode, singleModel: string, compareModelsRestore?: string[], overridesRestore?: Record<string, Record<string, string>>): void => {
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
- props.showToast('已恢复该次生成的配置,可直接再次生成')
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={dataUrlOf(audio)} itemKey={`${label}-${audio.id}`} />
872
+ <AudioPlayer src={srcOf(audio)} itemKey={`${label}-${audio.id}`} />
837
873
  <div className={css.audioCardActions}>
838
- <a className={css.ghostButton} href={dataUrlOf(audio)} download={`generated-${index + 1}.${audio.mime.split('/')[1]?.replace('mpeg', 'mp3') ?? 'mp3'}`}>
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.8'
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 = {