dsh-audiogen 0.4.1 → 0.4.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/README.md +2 -0
- package/lib/client.js +1669 -987
- package/lib/client.js.map +1 -1
- package/lib/index.js +124 -6
- package/package.json +1 -1
- package/src/agent-audio-tools.ts +11 -1
- package/src/audio-scheduler.ts +73 -0
- package/src/client/SettingsCard.tsx +18 -0
- package/src/client/api.ts +24 -25
- package/src/client/audio-panel.module.css +241 -0
- package/src/client/field-specs.ts +186 -0
- package/src/client/library-view.tsx +6 -1
- package/src/client/locales.ts +2 -0
- package/src/client/settings-scope.ts +18 -3
- package/src/client/studio-view.tsx +729 -390
- package/src/index.ts +14 -0
- package/src/protocol.ts +10 -1
- package/src/routes.ts +50 -2
|
@@ -425,6 +425,116 @@
|
|
|
425
425
|
background: color-mix(in srgb, var(--dsw-alias-brand-primary, #2563eb) 8%, transparent);
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
+
/* ---------------------------------------------------------- model compare */
|
|
429
|
+
|
|
430
|
+
.compareBox {
|
|
431
|
+
display: flex;
|
|
432
|
+
flex-direction: column;
|
|
433
|
+
gap: 7px;
|
|
434
|
+
padding: 9px 11px;
|
|
435
|
+
border: 1px dashed var(--dsw-alias-brand-primary, #2563eb);
|
|
436
|
+
border-radius: 10px;
|
|
437
|
+
background: color-mix(in srgb, var(--dsw-alias-brand-primary, #2563eb) 4%, var(--dsw-alias-bg-layer-1, #fff));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.compareChips {
|
|
441
|
+
display: flex;
|
|
442
|
+
flex-wrap: wrap;
|
|
443
|
+
gap: 6px;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.compareChip {
|
|
447
|
+
display: inline-flex;
|
|
448
|
+
align-items: center;
|
|
449
|
+
min-height: 27px;
|
|
450
|
+
padding: 0 11px;
|
|
451
|
+
border: 1px solid var(--dsw-alias-border-l2, #d1d5db);
|
|
452
|
+
border-radius: 999px;
|
|
453
|
+
background: var(--dsw-alias-bg-layer-1, #fff);
|
|
454
|
+
color: var(--dsw-alias-label-secondary, #6b7280);
|
|
455
|
+
font-family: inherit;
|
|
456
|
+
font-size: 12px;
|
|
457
|
+
cursor: pointer;
|
|
458
|
+
transition: color 0.12s, border-color 0.12s, background 0.12s;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.compareChip:hover {
|
|
462
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
463
|
+
border-color: var(--dsw-alias-label-dimmed, #9ca3af);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.compareChip[data-active='true'] {
|
|
467
|
+
color: var(--dsw-alias-brand-primary, #2563eb);
|
|
468
|
+
border-color: var(--dsw-alias-brand-primary, #2563eb);
|
|
469
|
+
background: color-mix(in srgb, var(--dsw-alias-brand-primary, #2563eb) 9%, transparent);
|
|
470
|
+
font-weight: 600;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.compareBoard {
|
|
474
|
+
display: flex;
|
|
475
|
+
flex-direction: column;
|
|
476
|
+
gap: 12px;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.compareGroup {
|
|
480
|
+
display: flex;
|
|
481
|
+
flex-direction: column;
|
|
482
|
+
gap: 8px;
|
|
483
|
+
padding: 11px;
|
|
484
|
+
border: 1px solid var(--dsw-alias-border-l1, #e5e7eb);
|
|
485
|
+
border-radius: 12px;
|
|
486
|
+
background: var(--dsw-alias-bg-layer-2, #f7f7f8);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.compareGroup[data-state='running'] {
|
|
490
|
+
border-color: color-mix(in srgb, var(--dsw-alias-brand-primary, #2563eb) 45%, var(--dsw-alias-border-l1, #e5e7eb));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.compareGroup[data-state='done'] {
|
|
494
|
+
border-color: color-mix(in srgb, #10b981 45%, var(--dsw-alias-border-l1, #e5e7eb));
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.compareGroup[data-state='error'] {
|
|
498
|
+
border-color: color-mix(in srgb, var(--dsw-alias-label-error, #b91c1c) 45%, var(--dsw-alias-border-l1, #e5e7eb));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.compareGroupHead {
|
|
502
|
+
display: flex;
|
|
503
|
+
align-items: center;
|
|
504
|
+
justify-content: space-between;
|
|
505
|
+
gap: 10px;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.compareModelName {
|
|
509
|
+
overflow: hidden;
|
|
510
|
+
padding: 3px 10px;
|
|
511
|
+
border-radius: 999px;
|
|
512
|
+
background: var(--dsw-alias-bg-layer-1, #fff);
|
|
513
|
+
border: 1px solid var(--dsw-alias-border-l1, #e5e7eb);
|
|
514
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
515
|
+
font-size: 12px;
|
|
516
|
+
font-weight: 600;
|
|
517
|
+
text-overflow: ellipsis;
|
|
518
|
+
white-space: nowrap;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.compareState {
|
|
522
|
+
display: inline-flex;
|
|
523
|
+
align-items: center;
|
|
524
|
+
gap: 5px;
|
|
525
|
+
font-size: 11px;
|
|
526
|
+
color: var(--dsw-alias-label-tertiary, #9ca3af);
|
|
527
|
+
white-space: nowrap;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.compareGroup[data-state='running'] .compareState { color: var(--dsw-alias-brand-primary, #2563eb); }
|
|
531
|
+
.compareGroup[data-state='done'] .compareState { color: #059669; }
|
|
532
|
+
.compareGroup[data-state='error'] .compareState { color: var(--dsw-alias-label-error, #b91c1c); }
|
|
533
|
+
|
|
534
|
+
.hint[data-error] {
|
|
535
|
+
color: var(--dsw-alias-label-error, #b91c1c);
|
|
536
|
+
}
|
|
537
|
+
|
|
428
538
|
/* --------------------------------------------------------------- history */
|
|
429
539
|
|
|
430
540
|
.historyCol {
|
|
@@ -892,3 +1002,134 @@
|
|
|
892
1002
|
color: var(--dsw-alias-label-primary, #1f2328);
|
|
893
1003
|
white-space: normal;
|
|
894
1004
|
}
|
|
1005
|
+
|
|
1006
|
+
/* 任务列表 */
|
|
1007
|
+
.taskList {
|
|
1008
|
+
display: flex;
|
|
1009
|
+
flex-direction: column;
|
|
1010
|
+
gap: 14px;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
.taskCard {
|
|
1014
|
+
border: 1px solid var(--dsw-alias-border-l2, #d1d5db);
|
|
1015
|
+
border-radius: 10px;
|
|
1016
|
+
padding: 10px 12px;
|
|
1017
|
+
display: flex;
|
|
1018
|
+
flex-direction: column;
|
|
1019
|
+
gap: 8px;
|
|
1020
|
+
background: var(--dsw-alias-bg-layer-2, #fafafa);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
.taskCard[data-state='failed'] {
|
|
1024
|
+
border-color: #dc2626;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
.taskCard[data-state='cancelled'] {
|
|
1028
|
+
opacity: 0.75;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
.taskHead {
|
|
1032
|
+
display: flex;
|
|
1033
|
+
align-items: center;
|
|
1034
|
+
gap: 8px;
|
|
1035
|
+
flex-wrap: wrap;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
.taskLabel {
|
|
1039
|
+
font-size: 13px;
|
|
1040
|
+
font-weight: 600;
|
|
1041
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
1042
|
+
overflow: hidden;
|
|
1043
|
+
text-overflow: ellipsis;
|
|
1044
|
+
white-space: nowrap;
|
|
1045
|
+
max-width: 200px;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
.taskStatus {
|
|
1049
|
+
font-size: 12px;
|
|
1050
|
+
color: var(--dsw-alias-label-secondary, #6b7280);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
.taskStatus[data-state='done'] {
|
|
1054
|
+
color: #16a34a;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
.taskStatus[data-state='failed'] {
|
|
1058
|
+
color: #dc2626;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
.taskActions {
|
|
1062
|
+
margin-left: auto;
|
|
1063
|
+
display: flex;
|
|
1064
|
+
gap: 6px;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/* 历史记录分类 */
|
|
1068
|
+
.historyTabs {
|
|
1069
|
+
display: flex;
|
|
1070
|
+
flex-wrap: wrap;
|
|
1071
|
+
gap: 6px;
|
|
1072
|
+
margin-bottom: 10px;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
.historyTab {
|
|
1076
|
+
border: 1px solid var(--dsw-alias-border-l2, #d1d5db);
|
|
1077
|
+
border-radius: 999px;
|
|
1078
|
+
padding: 3px 10px;
|
|
1079
|
+
font-size: 12px;
|
|
1080
|
+
cursor: pointer;
|
|
1081
|
+
background: var(--dsw-alias-bg-layer-3, #fff);
|
|
1082
|
+
color: var(--dsw-alias-label-secondary, #6b7280);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
.historyTab[data-active='true'] {
|
|
1086
|
+
background: var(--dsw-alias-bg-hover, #f3f4f6);
|
|
1087
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
1088
|
+
font-weight: 600;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
.historyTabCount {
|
|
1092
|
+
margin-left: 4px;
|
|
1093
|
+
font-size: 11px;
|
|
1094
|
+
color: var(--dsw-alias-label-tertiary, #9ca3af);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/* 对比任务卡 */
|
|
1098
|
+
.historyCompareSummary {
|
|
1099
|
+
display: flex;
|
|
1100
|
+
align-items: center;
|
|
1101
|
+
gap: 8px;
|
|
1102
|
+
cursor: pointer;
|
|
1103
|
+
flex-wrap: wrap;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
.historyCompareBadge {
|
|
1107
|
+
font-size: 11px;
|
|
1108
|
+
font-weight: 600;
|
|
1109
|
+
color: #7c3aed;
|
|
1110
|
+
border: 1px solid #ddd6fe;
|
|
1111
|
+
border-radius: 999px;
|
|
1112
|
+
padding: 1px 8px;
|
|
1113
|
+
background: #f5f3ff;
|
|
1114
|
+
white-space: nowrap;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
.historyModelRow {
|
|
1118
|
+
margin-top: 8px;
|
|
1119
|
+
padding-top: 8px;
|
|
1120
|
+
border-top: 1px dashed var(--dsw-alias-border-l1, #e5e7eb);
|
|
1121
|
+
display: flex;
|
|
1122
|
+
flex-direction: column;
|
|
1123
|
+
gap: 4px;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
.overrideOnly {
|
|
1127
|
+
font-size: 10px;
|
|
1128
|
+
color: var(--dsw-alias-label-tertiary, #9ca3af);
|
|
1129
|
+
font-weight: 500;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
.overrideDash {
|
|
1133
|
+
color: var(--dsw-alias-label-tertiary, #9ca3af);
|
|
1134
|
+
padding: 0 6px;
|
|
1135
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 厂商感知的字段规格表:按(渠道 preset × 模式)声明面板该显示哪些参数,
|
|
3
|
+
* 以及每个参数适用于哪些渠道。引擎不变——引擎本来就按渠道消费字段;
|
|
4
|
+
* 这里只负责「该显示什么、标签/枚举/校验按渠道」。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { AudioMode } from '../protocol.ts'
|
|
8
|
+
|
|
9
|
+
/** 与面板表单 state 对应的一组字段键。 */
|
|
10
|
+
export type FieldKey =
|
|
11
|
+
| 'duration'
|
|
12
|
+
| 'format'
|
|
13
|
+
| 'lyrics'
|
|
14
|
+
| 'instrumental'
|
|
15
|
+
| 'sampleRate'
|
|
16
|
+
| 'bitrate'
|
|
17
|
+
| 'audioChannel'
|
|
18
|
+
| 'voice'
|
|
19
|
+
| 'speed'
|
|
20
|
+
| 'emotion'
|
|
21
|
+
| 'vol'
|
|
22
|
+
| 'pitch'
|
|
23
|
+
| 'toneText'
|
|
24
|
+
| 'subtitle'
|
|
25
|
+
| 'loop'
|
|
26
|
+
| 'promptInfluence'
|
|
27
|
+
| 'seed'
|
|
28
|
+
| 'steps'
|
|
29
|
+
| 'cfgScale'
|
|
30
|
+
|
|
31
|
+
export interface FieldSpec {
|
|
32
|
+
key: FieldKey
|
|
33
|
+
label: string
|
|
34
|
+
type: 'text' | 'number' | 'select' | 'checkbox'
|
|
35
|
+
options?: string[]
|
|
36
|
+
placeholder?: string
|
|
37
|
+
min?: number
|
|
38
|
+
max?: number
|
|
39
|
+
step?: number
|
|
40
|
+
/** 官方字段名/说明(tooltip)。 */
|
|
41
|
+
hint?: string
|
|
42
|
+
/** 放在折叠「高级参数」里。 */
|
|
43
|
+
advanced?: boolean
|
|
44
|
+
/** 适用的渠道 preset;undefined 表示通用。 */
|
|
45
|
+
presets?: string[]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const PRESET_MINIMAX = 'minimax'
|
|
49
|
+
const PRESET_ELEVENLABS = 'elevenlabs'
|
|
50
|
+
const PRESET_STABILITY = 'stability-audio'
|
|
51
|
+
|
|
52
|
+
/** 各 preset 支持的音乐输出格式(交集用于全局字段)。 */
|
|
53
|
+
const MUSIC_FORMATS: Record<string, string[]> = {
|
|
54
|
+
[PRESET_MINIMAX]: ['mp3', 'wav', 'pcm'],
|
|
55
|
+
[PRESET_ELEVENLABS]: ['mp3', 'wav'],
|
|
56
|
+
[PRESET_STABILITY]: ['mp3', 'wav'],
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const MUSIC_KEYS: FieldKey[] = ['duration', 'format', 'lyrics', 'instrumental', 'sampleRate', 'bitrate']
|
|
60
|
+
const TTS_KEYS: FieldKey[] = ['voice', 'speed', 'format', 'emotion', 'vol', 'pitch', 'toneText', 'sampleRate', 'bitrate', 'audioChannel', 'subtitle']
|
|
61
|
+
const SFX_KEYS: FieldKey[] = ['duration', 'format', 'loop', 'promptInfluence', 'seed', 'steps', 'cfgScale']
|
|
62
|
+
|
|
63
|
+
export function presetSupports(preset: string, key: FieldKey, mode: AudioMode): boolean {
|
|
64
|
+
const p = preset.toLowerCase()
|
|
65
|
+
const K = MUSIC_KEYS.includes(key) ? MUSIC_KEYS : TTS_KEYS.includes(key) ? TTS_KEYS : SFX_KEYS
|
|
66
|
+
if (!K.includes(key)) return true
|
|
67
|
+
switch (key) {
|
|
68
|
+
case 'lyrics':
|
|
69
|
+
case 'instrumental':
|
|
70
|
+
return p === PRESET_MINIMAX || p === PRESET_ELEVENLABS
|
|
71
|
+
case 'sampleRate':
|
|
72
|
+
case 'bitrate':
|
|
73
|
+
case 'audioChannel':
|
|
74
|
+
return p === PRESET_MINIMAX
|
|
75
|
+
case 'emotion':
|
|
76
|
+
case 'vol':
|
|
77
|
+
case 'pitch':
|
|
78
|
+
case 'toneText':
|
|
79
|
+
case 'subtitle':
|
|
80
|
+
return p === PRESET_MINIMAX
|
|
81
|
+
case 'loop':
|
|
82
|
+
case 'promptInfluence':
|
|
83
|
+
return p === PRESET_ELEVENLABS
|
|
84
|
+
case 'seed':
|
|
85
|
+
case 'steps':
|
|
86
|
+
case 'cfgScale':
|
|
87
|
+
return p === PRESET_STABILITY
|
|
88
|
+
default:
|
|
89
|
+
return true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const SPECS: Record<FieldKey, Omit<FieldSpec, 'key' | 'presets'>> = {
|
|
94
|
+
duration: { label: '时长(秒)', type: 'number', min: 1, max: 120, placeholder: '30', hint: 'duration;MiniMax 音乐 ≤190、ElevenLabs 音乐 3-600(转 ms)、Stability 按模型 190/380' },
|
|
95
|
+
format: { label: '输出格式', type: 'select', options: ['mp3', 'wav', 'pcm', 'flac', 'ogg'], hint: 'format / output_format / response_format' },
|
|
96
|
+
lyrics: { label: '歌词(纯音乐模式可留空;多段用空行分隔)', type: 'text', placeholder: '第一段歌词…\n\n第二段歌词…', hint: 'MiniMax lyrics / ElevenLabs lyrics_text' },
|
|
97
|
+
instrumental: { label: '纯音乐(无歌词/人声)', type: 'checkbox', hint: 'MiniMax is_instrumental / ElevenLabs force_instrumental' },
|
|
98
|
+
sampleRate: { label: '采样率', type: 'select', options: ['16000', '24000', '32000', '44100'], placeholder: '默认(44100)', hint: 'audio_setting.sample_rate:16000-44100' },
|
|
99
|
+
bitrate: { label: '码率 bps', type: 'select', options: ['32000', '64000', '128000', '256000'], placeholder: '默认(256000)', hint: 'audio_setting.bitrate:32000-256000' },
|
|
100
|
+
audioChannel: { label: '声道', type: 'select', options: ['1', '2'], placeholder: '默认(1)', hint: 'audio_setting.channel(TTS)' },
|
|
101
|
+
voice: { label: '音色', type: 'text', placeholder: '自定义音色', hint: 'voice_id(MiniMax 必填)/ voice(ElevenLabs)' },
|
|
102
|
+
speed: { label: '语速', type: 'number', min: 0.5, max: 2, step: 0.1, placeholder: '1.0', hint: 'speed(MiniMax 0.5-2)' },
|
|
103
|
+
emotion: { label: '情绪 emotion', type: 'text', placeholder: 'happy / sad / angry / nervous…', hint: 'voice_setting.emotion', advanced: true },
|
|
104
|
+
vol: { label: '音量 vol (0-10)', type: 'number', min: 0, max: 10, step: 0.5, placeholder: '1', hint: 'voice_setting.vol', advanced: true },
|
|
105
|
+
pitch: { label: '音调 pitch (-12~12)', type: 'number', min: -12, max: 12, placeholder: '0', hint: 'voice_setting.pitch', advanced: true },
|
|
106
|
+
toneText: { label: '发音词典(每行一条:"文字/读音")', type: 'text', placeholder: '处理/(chu3)(li3)\n危险/dangerous', hint: 'pronunciation_dict.tone', advanced: true },
|
|
107
|
+
subtitle: { label: '生成字幕 subtitle_enable', type: 'checkbox', hint: 'subtitle_enable', advanced: true },
|
|
108
|
+
loop: { label: '循环音效 loop(无缝循环)', type: 'checkbox', hint: 'ElevenLabs loop(仅 eleven_text_to_sound_v2)' },
|
|
109
|
+
promptInfluence: { label: '提示词影响度 prompt_influence (0-1)', type: 'number', min: 0, max: 1, step: 0.1, placeholder: '0.3', hint: 'prompt_influence:越高越贴提示词' },
|
|
110
|
+
seed: { label: 'seed', type: 'number', min: 0, max: 4294967294, placeholder: '默认(随机)', hint: 'Stable Audio seed(同参数可复现)' },
|
|
111
|
+
steps: { label: 'steps', type: 'number', min: 4, max: 100, placeholder: '默认', hint: 'Stable Audio 采样步数(2: 30-100;2.5/3: 4-8)' },
|
|
112
|
+
cfgScale: { label: 'cfg_scale', type: 'number', min: 1, max: 25, placeholder: '默认', hint: 'Stable Audio 提示词遵循度(2 默认 7,2.5/3 默认 1)' },
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function specOf(key: FieldKey, presets?: string[]): FieldSpec {
|
|
116
|
+
return { key, ...SPECS[key], ...(presets === undefined ? {} : { presets }) }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** 当前模式 + 所选模型集合(渠道集合)对应的「全局字段」清单。 */
|
|
120
|
+
export function globalFieldSpecs(mode: AudioMode, presets: string[]): FieldSpec[] {
|
|
121
|
+
const list: FieldSpec[] = []
|
|
122
|
+
const all = (key: FieldKey, keys: FieldKey[]): boolean => keys.includes(key) && presets.every(preset => presetSupports(preset, key, mode))
|
|
123
|
+
if (mode === 'tts') {
|
|
124
|
+
list.push(specOf('voice'), specOf('speed'))
|
|
125
|
+
list.push({ ...specOf('format'), options: ['mp3', 'wav', 'flac', 'ogg', 'pcm'] })
|
|
126
|
+
const advanced = (['emotion', 'vol', 'pitch', 'toneText', 'sampleRate', 'bitrate', 'audioChannel', 'subtitle'] as FieldKey[]).filter(key => all(key, TTS_KEYS) && presets.length > 0)
|
|
127
|
+
for (const key of advanced) list.push(specOf(key))
|
|
128
|
+
} else if (mode === 'music') {
|
|
129
|
+
list.push(specOf('duration'))
|
|
130
|
+
// 格式选项取所有选中渠道支持的交集(无交集则回退 mp3/wav)
|
|
131
|
+
const supported: string[][] = presets.length === 0 ? [['mp3', 'wav', 'pcm']] : presets.map(preset => MUSIC_FORMATS[preset.toLowerCase()] ?? ['mp3', 'wav'])
|
|
132
|
+
const intersect: string[] = supported.reduce<string[]>((acc, cur) => acc.filter(item => cur.includes(item)), supported[0] ?? ['mp3', 'wav'])
|
|
133
|
+
list.push({ ...specOf('format'), options: intersect.length > 0 ? intersect : ['mp3', 'wav'] })
|
|
134
|
+
if (all('lyrics', MUSIC_KEYS) && presets.length > 0) list.push(specOf('lyrics'))
|
|
135
|
+
if (all('instrumental', MUSIC_KEYS) && presets.length > 0) list.push(specOf('instrumental'))
|
|
136
|
+
if (all('sampleRate', MUSIC_KEYS) && presets.length > 0) list.push(specOf('sampleRate'))
|
|
137
|
+
if (all('bitrate', MUSIC_KEYS) && presets.length > 0) list.push(specOf('bitrate'))
|
|
138
|
+
} else if (mode === 'sfx') {
|
|
139
|
+
list.push(specOf('duration'))
|
|
140
|
+
list.push({ ...specOf('format'), options: ['mp3', 'wav', 'pcm'] })
|
|
141
|
+
if (all('loop', SFX_KEYS) && presets.length > 0) list.push(specOf('loop'))
|
|
142
|
+
if (all('promptInfluence', SFX_KEYS) && presets.length > 0) list.push(specOf('promptInfluence'))
|
|
143
|
+
if (all('seed', SFX_KEYS) && presets.length > 0) list.push(specOf('seed'), specOf('steps'), specOf('cfgScale'))
|
|
144
|
+
}
|
|
145
|
+
return list
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** 「每模型参数覆盖」矩阵的字段全集(含适用渠道标注)。 */
|
|
149
|
+
export function overrideRowSpecs(mode: AudioMode): Array<FieldSpec & { presets: string[] }> {
|
|
150
|
+
const rows: Array<FieldSpec & { presets: string[] }> = []
|
|
151
|
+
const presets: Array<[FieldKey, string[]]> = [
|
|
152
|
+
['format', [PRESET_MINIMAX, PRESET_ELEVENLABS, PRESET_STABILITY]],
|
|
153
|
+
['duration', [PRESET_MINIMAX, PRESET_ELEVENLABS, PRESET_STABILITY]],
|
|
154
|
+
['voice', [PRESET_MINIMAX, PRESET_ELEVENLABS]],
|
|
155
|
+
['speed', [PRESET_MINIMAX, PRESET_ELEVENLABS]],
|
|
156
|
+
['lyrics', [PRESET_MINIMAX, PRESET_ELEVENLABS]],
|
|
157
|
+
['instrumental', [PRESET_MINIMAX, PRESET_ELEVENLABS]],
|
|
158
|
+
['sampleRate', [PRESET_MINIMAX]],
|
|
159
|
+
['bitrate', [PRESET_MINIMAX]],
|
|
160
|
+
['audioChannel', [PRESET_MINIMAX]],
|
|
161
|
+
['emotion', [PRESET_MINIMAX]],
|
|
162
|
+
['vol', [PRESET_MINIMAX]],
|
|
163
|
+
['pitch', [PRESET_MINIMAX]],
|
|
164
|
+
['toneText', [PRESET_MINIMAX]],
|
|
165
|
+
['subtitle', [PRESET_MINIMAX]],
|
|
166
|
+
['loop', [PRESET_ELEVENLABS]],
|
|
167
|
+
['promptInfluence', [PRESET_ELEVENLABS]],
|
|
168
|
+
['seed', [PRESET_STABILITY]],
|
|
169
|
+
['steps', [PRESET_STABILITY]],
|
|
170
|
+
['cfgScale', [PRESET_STABILITY]],
|
|
171
|
+
]
|
|
172
|
+
for (const [key, applicable] of presets) {
|
|
173
|
+
const spec = specOf(key, applicable)
|
|
174
|
+
rows.push({ ...spec, presets: applicable })
|
|
175
|
+
}
|
|
176
|
+
return rows
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** 渠道 preset 的展示名。 */
|
|
180
|
+
export function presetLabel(preset: string): string {
|
|
181
|
+
if (preset === PRESET_MINIMAX) return 'MiniMax'
|
|
182
|
+
if (preset === PRESET_ELEVENLABS) return 'ElevenLabs'
|
|
183
|
+
if (preset === PRESET_STABILITY) return 'Stability'
|
|
184
|
+
if (preset === 'openai-tts') return 'OpenAI'
|
|
185
|
+
return '自定义'
|
|
186
|
+
}
|
|
@@ -319,7 +319,12 @@ export function LibraryView(props: {
|
|
|
319
319
|
</div>
|
|
320
320
|
|
|
321
321
|
{loading ? <p className={css.stateNote}>加载中…</p> : null}
|
|
322
|
-
{error !== null ?
|
|
322
|
+
{error !== null ? (
|
|
323
|
+
<div className={css.stateNote} data-error>
|
|
324
|
+
<p>{error}</p>
|
|
325
|
+
<p>若提示 not found / 404:插件宿主尚未加载新代码,请重启 `dsh web` 后在浏览器强制刷新(Cmd+Shift+R)。</p>
|
|
326
|
+
</div>
|
|
327
|
+
) : null}
|
|
323
328
|
{!loading && error === null && filtered.length === 0 ? (
|
|
324
329
|
<div className={css.empty}>
|
|
325
330
|
<span className={css.emptyIcon}>🎧</span>
|
package/src/client/locales.ts
CHANGED
|
@@ -36,6 +36,7 @@ export const zh = {
|
|
|
36
36
|
'settings.announceToAgent': '向 Agent 播报本插件',
|
|
37
37
|
'settings.allowAgentAudio': '允许 Agent 调用音频生成',
|
|
38
38
|
'settings.autoSaveLibrary': '生成后自动保存到资源库',
|
|
39
|
+
'settings.maxConcurrent': '最大并发生成数(同时打到上游的请求数,默认 5)',
|
|
39
40
|
'settings.save': '保存',
|
|
40
41
|
'settings.saving': '保存中…',
|
|
41
42
|
'settings.discard': '放弃修改',
|
|
@@ -140,6 +141,7 @@ export const en: Record<AudioGenKey, string> = {
|
|
|
140
141
|
'settings.announceToAgent': 'Announce this plugin to agents',
|
|
141
142
|
'settings.allowAgentAudio': 'Allow agents to generate audio',
|
|
142
143
|
'settings.autoSaveLibrary': 'Auto-save generated audio to the library',
|
|
144
|
+
'settings.maxConcurrent': 'Max concurrent generations (in-flight upstream calls, default 5)',
|
|
143
145
|
'settings.save': 'Save',
|
|
144
146
|
'settings.saving': 'Saving…',
|
|
145
147
|
'settings.discard': 'Discard',
|
|
@@ -271,8 +271,17 @@ export function bindAudiogenScope(fetchFn: typeof fetch = fetch): AudiogenScope
|
|
|
271
271
|
* Falls back to the legacy flat allow-list while no channels exist (upgrade
|
|
272
272
|
* path). Pure projection — no host calls.
|
|
273
273
|
*/
|
|
274
|
+
export interface ModelOption {
|
|
275
|
+
alias: string
|
|
276
|
+
category?: AudioModelCategory
|
|
277
|
+
/** 所属渠道 id/名称/preset(面板按渠道渲染参数与分组下拉)。 */
|
|
278
|
+
channelId: string
|
|
279
|
+
channelName: string
|
|
280
|
+
preset: string
|
|
281
|
+
}
|
|
282
|
+
|
|
274
283
|
export function audioModelOptions(config: AudiogenConfig | undefined): {
|
|
275
|
-
models:
|
|
284
|
+
models: ModelOption[]
|
|
276
285
|
defaultChannelId?: string
|
|
277
286
|
} {
|
|
278
287
|
const channels = config?.channels ?? []
|
|
@@ -283,14 +292,20 @@ export function audioModelOptions(config: AudiogenConfig | undefined): {
|
|
|
283
292
|
? config.defaultChannelId
|
|
284
293
|
: channels[0]!.id
|
|
285
294
|
const ordered = [defaultId, ...channels.filter(channel => channel.id !== defaultId).map(channel => channel.id)]
|
|
286
|
-
const models:
|
|
295
|
+
const models: ModelOption[] = []
|
|
287
296
|
const seen = new Set<string>()
|
|
288
297
|
for (const id of ordered) {
|
|
289
298
|
const channel = channels.find(candidate => candidate.id === id)!
|
|
290
299
|
for (const model of channel.models) {
|
|
291
300
|
if (model.alias === '' || seen.has(model.alias)) continue
|
|
292
301
|
seen.add(model.alias)
|
|
293
|
-
models.push({
|
|
302
|
+
models.push({
|
|
303
|
+
alias: model.alias,
|
|
304
|
+
...(model.category === undefined ? {} : { category: model.category }),
|
|
305
|
+
channelId: channel.id,
|
|
306
|
+
channelName: channel.name,
|
|
307
|
+
preset: channel.preset,
|
|
308
|
+
})
|
|
294
309
|
}
|
|
295
310
|
}
|
|
296
311
|
return models.length > 0 ? { models, defaultChannelId: defaultId } : { models: [], defaultChannelId: defaultId }
|