dsh-audiogen 0.1.0
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/LICENSE +201 -0
- package/README.md +67 -0
- package/cordis.patch.yml +8 -0
- package/lib/client.js +2457 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +1345 -0
- package/package.json +93 -0
- package/skills/design/SKILL.md +13 -0
- package/skills/music/SKILL.md +16 -0
- package/skills/sfx/SKILL.md +16 -0
- package/skills/tts/SKILL.md +18 -0
- package/src/agent-audio-tools.ts +190 -0
- package/src/audio-engine.ts +377 -0
- package/src/audio-presets.ts +80 -0
- package/src/audio-store.ts +131 -0
- package/src/client/AudioGenPanel.tsx +206 -0
- package/src/client/SettingsCard.tsx +337 -0
- package/src/client/api.ts +36 -0
- package/src/client/audio-panel.module.css +198 -0
- package/src/client/audio-toolview.module.css +69 -0
- package/src/client/audio-toolview.tsx +119 -0
- package/src/client/channels-form.ts +263 -0
- package/src/client/controller.ts +44 -0
- package/src/client/css-modules.d.ts +5 -0
- package/src/client/helpers.ts +27 -0
- package/src/client/index.ts +103 -0
- package/src/client/locales.ts +133 -0
- package/src/client/mount.tsx +96 -0
- package/src/client/panel.module.css +1566 -0
- package/src/client/settings-card.module.css +1023 -0
- package/src/client/settings-form.ts +336 -0
- package/src/client/settings-scope.ts +289 -0
- package/src/client/sidebar-entry.ts +115 -0
- package/src/index.ts +201 -0
- package/src/protocol.ts +179 -0
- package/src/routes.ts +386 -0
package/package.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-audiogen",
|
|
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.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"dsh": {
|
|
13
|
+
"bundle": {
|
|
14
|
+
"patch": "./cordis.patch.yml"
|
|
15
|
+
},
|
|
16
|
+
"client": {
|
|
17
|
+
"inject": [
|
|
18
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
19
|
+
"@deepseek-ai/dsh-client-connection",
|
|
20
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
21
|
+
"@deepseek-ai/dsh-client-ui-tool"
|
|
22
|
+
],
|
|
23
|
+
"platform": "web"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"schemastery": "^3.18.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
31
|
+
"@deepseek-ai/dsh-client-connection": "0.1.0-rc.7",
|
|
32
|
+
"@deepseek-ai/dsh-client-locale": "0.1.0-rc.7",
|
|
33
|
+
"@deepseek-ai/dsh-client-runtime": "0.1.0-rc.7",
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.0-rc.7",
|
|
35
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.7",
|
|
36
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.7",
|
|
37
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.0-rc.7",
|
|
38
|
+
"@deepseek-ai/dsh-llm": "0.1.0-rc.7",
|
|
39
|
+
"@deepseek-ai/dsh-settings": "0.1.0-rc.7",
|
|
40
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.0-rc.7",
|
|
41
|
+
"@deepseek-ai/dsh-tools": "0.1.0-rc.7",
|
|
42
|
+
"@types/node": "^22.20.0",
|
|
43
|
+
"@types/react": "~18.3.1",
|
|
44
|
+
"@types/react-dom": "^18.3.5",
|
|
45
|
+
"jsdom": "25.0.1",
|
|
46
|
+
"lightningcss": "1.32.0",
|
|
47
|
+
"react": "^18.3.1",
|
|
48
|
+
"react-dom": "^18.3.1",
|
|
49
|
+
"schemastery": "^3.18.0",
|
|
50
|
+
"tsdown": "0.22.2",
|
|
51
|
+
"typescript": "~5.7.2"
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"lib",
|
|
55
|
+
"src",
|
|
56
|
+
"skills",
|
|
57
|
+
"cordis.patch.yml",
|
|
58
|
+
"README.md"
|
|
59
|
+
],
|
|
60
|
+
"license": "Apache-2.0",
|
|
61
|
+
"repository": {
|
|
62
|
+
"type": "git",
|
|
63
|
+
"url": "git+https://github.com/shimingming520/dsh-audiogen.git"
|
|
64
|
+
},
|
|
65
|
+
"homepage": "https://github.com/shimingming520/dsh-audiogen",
|
|
66
|
+
"bugs": {
|
|
67
|
+
"url": "https://github.com/shimingming520/dsh-audiogen/issues"
|
|
68
|
+
},
|
|
69
|
+
"keywords": [
|
|
70
|
+
"dsh",
|
|
71
|
+
"deepseek-harness",
|
|
72
|
+
"plugin",
|
|
73
|
+
"audio",
|
|
74
|
+
"tts",
|
|
75
|
+
"music",
|
|
76
|
+
"sfx",
|
|
77
|
+
"elevenlabs",
|
|
78
|
+
"minimax",
|
|
79
|
+
"stability"
|
|
80
|
+
],
|
|
81
|
+
"publishConfig": {
|
|
82
|
+
"access": "public"
|
|
83
|
+
},
|
|
84
|
+
"engines": {
|
|
85
|
+
"node": ">=20"
|
|
86
|
+
},
|
|
87
|
+
"scripts": {
|
|
88
|
+
"build": "tsdown",
|
|
89
|
+
"watch": "tsdown --watch",
|
|
90
|
+
"typecheck": "tsc --noEmit",
|
|
91
|
+
"prepublishOnly": "pnpm run typecheck && pnpm run build"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# 音色/音效设计
|
|
2
|
+
|
|
3
|
+
## 触发
|
|
4
|
+
- `/audio:design <描述>`
|
|
5
|
+
|
|
6
|
+
## 用途
|
|
7
|
+
- 帮助用户把音色或音效需求细化成可复用的描述,供后续 TTS/music/sfx 生成。
|
|
8
|
+
- 输出不建议直接生成音频,而是给出音色参数建议和可用的模型/音色清单。
|
|
9
|
+
|
|
10
|
+
## 流程
|
|
11
|
+
1. 询问目标风格、音域、情绪、适用场景。
|
|
12
|
+
2. 生成结构化的音色描述(如温暖复古合成器、未来感 UI 提示音)。
|
|
13
|
+
3. 在后续生成中复用该描述,必要时通过 `generate_audio` 试听。
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# 音乐生成
|
|
2
|
+
|
|
3
|
+
## 触发
|
|
4
|
+
- `/audio:music <描述>`
|
|
5
|
+
- 用户说“生成一段音乐 / 配乐 / BGM”
|
|
6
|
+
|
|
7
|
+
## 参数
|
|
8
|
+
- prompt: 必填,风格/情绪/乐器/时长描述
|
|
9
|
+
- model: 可选,已配置的音频模型
|
|
10
|
+
- duration: 可选,秒数
|
|
11
|
+
- format: 可选,mp3 / wav
|
|
12
|
+
|
|
13
|
+
## 流程
|
|
14
|
+
1. 确认已配置支持音乐生成的渠道(如 Stability Audio / 自定义)。
|
|
15
|
+
2. 调用 `generate_audio`,mode=music。
|
|
16
|
+
3. 将生成的音频 URL 返回给用户。
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# TTS 文本转语音
|
|
2
|
+
|
|
3
|
+
## 触发
|
|
4
|
+
- `/audio:tts <文本>`
|
|
5
|
+
- 用户说“朗读 / 配音 / 语音生成”
|
|
6
|
+
|
|
7
|
+
## 参数
|
|
8
|
+
- text: 必填,要朗读的文本
|
|
9
|
+
- model: 可选,已配置的模型/音色
|
|
10
|
+
- voice: 可选,音色
|
|
11
|
+
- speed: 可选,语速倍率
|
|
12
|
+
- format: 可选,mp3 / wav 等
|
|
13
|
+
|
|
14
|
+
## 流程
|
|
15
|
+
1. 确认已配置音频渠道(设置 → 插件 → AI 音频)。
|
|
16
|
+
2. 若用户未指定模型且有多个,先询问。
|
|
17
|
+
3. 调用 `generate_audio` 工具,mode=tts。
|
|
18
|
+
4. 把返回的音频 URL 提供给用户,可播放/下载。
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-facing audio-generation tool backed by the same host engine as the
|
|
3
|
+
* panel. Returns a compact JSON text result with same-origin audio URLs so the
|
|
4
|
+
* model can reference the generated audio without receiving binary payloads.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
8
|
+
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
9
|
+
import { randomUUID } from 'node:crypto'
|
|
10
|
+
import type { AudioChannel } from './audio-engine.ts'
|
|
11
|
+
import { generateAudio, AudioGenError } from './audio-engine.ts'
|
|
12
|
+
import { appendHistory, saveAudioFile } from './audio-store.ts'
|
|
13
|
+
import type { AudioMode, GenerateAudioRequest } from './protocol.ts'
|
|
14
|
+
|
|
15
|
+
export interface AgentAudioToolConfig {
|
|
16
|
+
enabled: boolean
|
|
17
|
+
allowAgentAudioGeneration: boolean
|
|
18
|
+
channels: AudioChannel[]
|
|
19
|
+
defaultChannelId: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface AgentAudioRef {
|
|
23
|
+
id: string
|
|
24
|
+
url: string
|
|
25
|
+
mime: string
|
|
26
|
+
bytes: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface AgentAudioResult {
|
|
30
|
+
status: string
|
|
31
|
+
message: string
|
|
32
|
+
mode: AudioMode
|
|
33
|
+
model: string
|
|
34
|
+
audio: AgentAudioRef[]
|
|
35
|
+
error?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const audioRefSchema = {
|
|
39
|
+
type: 'object',
|
|
40
|
+
additionalProperties: false,
|
|
41
|
+
properties: {
|
|
42
|
+
id: { type: 'string', required: true },
|
|
43
|
+
url: { type: 'string', required: true },
|
|
44
|
+
mime: { type: 'string', required: true },
|
|
45
|
+
bytes: { type: 'integer', required: true },
|
|
46
|
+
},
|
|
47
|
+
} as const
|
|
48
|
+
|
|
49
|
+
const resultSchema = {
|
|
50
|
+
type: 'object',
|
|
51
|
+
additionalProperties: false,
|
|
52
|
+
properties: {
|
|
53
|
+
status: { type: 'string', required: true },
|
|
54
|
+
message: { type: 'string', required: true },
|
|
55
|
+
mode: { type: 'string', required: true, enum: ['tts', 'music', 'sfx'] },
|
|
56
|
+
model: { type: 'string', required: true },
|
|
57
|
+
audio: { type: 'array', required: true, items: audioRefSchema },
|
|
58
|
+
error: { type: 'string' },
|
|
59
|
+
},
|
|
60
|
+
} as const
|
|
61
|
+
|
|
62
|
+
function renderResult(value: AgentAudioResult): Array<{ type: 'text'; text: string }> {
|
|
63
|
+
return [{ type: 'text', text: JSON.stringify(value) }]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolveModel(config: AgentAudioToolConfig, requested: unknown): {
|
|
67
|
+
channel: AudioChannel
|
|
68
|
+
alias: string
|
|
69
|
+
upstream: string
|
|
70
|
+
} {
|
|
71
|
+
const entries = config.channels.flatMap(channel => channel.models.map(model => ({ channel, alias: model.alias, upstream: model.id })))
|
|
72
|
+
if (entries.length === 0) {
|
|
73
|
+
throw new AudioGenError('No audio models/voices are configured. Open Settings > Plugins > AI Audio and add at least one.', 'no-models-configured')
|
|
74
|
+
}
|
|
75
|
+
const wanted = typeof requested === 'string' && requested.trim() !== '' ? requested.trim() : ''
|
|
76
|
+
if (wanted === '') {
|
|
77
|
+
if (entries.length === 1) return entries[0]!
|
|
78
|
+
const options = entries.map(entry => `"${entry.channel.name} · ${entry.alias}"`).join(', ')
|
|
79
|
+
throw new AudioGenError(`Multiple audio models/voices are available — ask the user which channel and model to use, then call again. Options: ${options}.`, 'model-choice-required')
|
|
80
|
+
}
|
|
81
|
+
const hosting = entries.filter(entry => entry.alias === wanted)
|
|
82
|
+
if (hosting.length === 0) {
|
|
83
|
+
const available = [...new Set(entries.map(entry => entry.alias))].join(', ')
|
|
84
|
+
throw new AudioGenError(`Audio model/voice "${wanted}" is not configured. Choose one of: ${available}.`, 'audio-model-not-configured')
|
|
85
|
+
}
|
|
86
|
+
const preferred = hosting.find(entry => entry.channel.id === config.defaultChannelId)
|
|
87
|
+
return preferred ?? hosting[0]!
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function ensureConfigured(config: AgentAudioToolConfig): void {
|
|
91
|
+
if (!config.enabled) throw new AudioGenError('AI audio generation is disabled. Open Settings > Plugins > AI Audio and enable it.', 'plugin-disabled')
|
|
92
|
+
if (!config.allowAgentAudioGeneration) throw new AudioGenError('Agent audio generation is disabled in Settings > Plugins > AI Audio.', 'agent-generation-disabled')
|
|
93
|
+
const usable = config.channels.some(channel => channel.apiUrl.trim() !== '' && channel.apiKey.trim() !== '')
|
|
94
|
+
if (!usable) throw new AudioGenError('Audio API credentials are not configured. Open Settings > Plugins > AI Audio, add a channel and fill its API URL and API key.', 'audio-api-not-configured')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Register the Agent audio tool. */
|
|
98
|
+
export function registerAgentAudioTools(ctx: Context, resolve: () => AgentAudioToolConfig): () => void {
|
|
99
|
+
const disposer = ctx.tools.register(defineTool({
|
|
100
|
+
name: 'generate_audio',
|
|
101
|
+
description: 'Generate audio with the configured audio provider. Supports text-to-speech, music generation and sound effects. The tool call waits for the upstream result and returns same-origin audio URLs; pass those URLs to the user for playback or download. If multiple models are configured, first ask the user which one to use or pass model explicitly.',
|
|
102
|
+
parameters: {
|
|
103
|
+
prompt: { type: 'string', required: true, description: 'For tts, the text to speak. For music/sfx, a descriptive prompt.' },
|
|
104
|
+
mode: { type: 'string', enum: ['tts', 'music', 'sfx'], description: 'Generation mode. Defaults to tts.' },
|
|
105
|
+
model: { type: 'string', description: 'One of the configured audio models/voices. Defaults to the first configured model.' },
|
|
106
|
+
voice: { type: 'string', description: 'Optional voice id/name for TTS providers.' },
|
|
107
|
+
speed: { type: 'number', description: 'Optional speaking rate / speed multiplier where supported.' },
|
|
108
|
+
duration: { type: 'number', description: 'Requested duration in seconds for music/sfx.' },
|
|
109
|
+
format: { type: 'string', description: 'Output format such as mp3 or wav.' },
|
|
110
|
+
},
|
|
111
|
+
output: {
|
|
112
|
+
schema: resultSchema,
|
|
113
|
+
render: (_args, value) => renderResult(value),
|
|
114
|
+
},
|
|
115
|
+
timeoutMs: 300_000,
|
|
116
|
+
isConcurrencySafe: () => true,
|
|
117
|
+
async execute(args, exec) {
|
|
118
|
+
const config = resolve()
|
|
119
|
+
ensureConfigured(config)
|
|
120
|
+
const picked = resolveModel(config, args.model)
|
|
121
|
+
const request: GenerateAudioRequest = {
|
|
122
|
+
mode: args.mode === 'music' ? 'music' : args.mode === 'sfx' ? 'sfx' : 'tts',
|
|
123
|
+
model: picked.alias,
|
|
124
|
+
upstream: picked.upstream,
|
|
125
|
+
channelId: picked.channel.id,
|
|
126
|
+
channel: picked.channel.name,
|
|
127
|
+
prompt: args.prompt.trim(),
|
|
128
|
+
...(typeof args.voice === 'string' && args.voice.trim() !== '' ? { voice: args.voice.trim() } : {}),
|
|
129
|
+
...(typeof args.speed === 'number' ? { speed: args.speed } : {}),
|
|
130
|
+
...(typeof args.duration === 'number' ? { duration: args.duration } : {}),
|
|
131
|
+
...(typeof args.format === 'string' && args.format.trim() !== '' ? { format: args.format.trim() } : {}),
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const outputs = await generateAudio(picked.channel, request, exec.signal)
|
|
135
|
+
const audio: AgentAudioRef[] = []
|
|
136
|
+
for (const [index, output] of outputs.entries()) {
|
|
137
|
+
const saved = await saveAudioFile(output.data, output.mime, `generated-${index + 1}`)
|
|
138
|
+
audio.push({
|
|
139
|
+
id: saved.id,
|
|
140
|
+
url: `/api/dsh-audiogen/audio/${encodeURIComponent(saved.file)}`,
|
|
141
|
+
mime: saved.mime,
|
|
142
|
+
bytes: saved.bytes,
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
await appendHistory({
|
|
147
|
+
id: randomUUID(),
|
|
148
|
+
createdAt: Date.now(),
|
|
149
|
+
mode: request.mode,
|
|
150
|
+
model: picked.alias,
|
|
151
|
+
prompt: request.prompt,
|
|
152
|
+
...(request.voice === undefined ? {} : { voice: request.voice }),
|
|
153
|
+
...(request.speed === undefined ? {} : { speed: request.speed }),
|
|
154
|
+
...(request.duration === undefined ? {} : { duration: request.duration }),
|
|
155
|
+
...(request.format === undefined ? {} : { format: request.format }),
|
|
156
|
+
audio: outputs.map((output, index) => ({
|
|
157
|
+
id: audio[index]!.id,
|
|
158
|
+
b64: Buffer.from(output.data).toString('base64'),
|
|
159
|
+
mime: audio[index]!.mime,
|
|
160
|
+
bytes: audio[index]!.bytes,
|
|
161
|
+
url: audio[index]!.url,
|
|
162
|
+
})),
|
|
163
|
+
channelId: picked.channel.id,
|
|
164
|
+
channel: picked.channel.name,
|
|
165
|
+
})
|
|
166
|
+
} catch {
|
|
167
|
+
// History is best-effort and must not fail the agent tool.
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
status: 'completed',
|
|
171
|
+
message: 'Audio generation completed. The audio files can be played/downloaded from the returned URLs.',
|
|
172
|
+
mode: request.mode,
|
|
173
|
+
model: picked.alias,
|
|
174
|
+
audio,
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (exec.signal?.aborted === true) throw error
|
|
178
|
+
return {
|
|
179
|
+
status: 'failed',
|
|
180
|
+
message: 'Audio generation failed.',
|
|
181
|
+
mode: request.mode,
|
|
182
|
+
model: picked.alias,
|
|
183
|
+
audio: [],
|
|
184
|
+
error: error instanceof Error ? error.message : String(error),
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
}))
|
|
189
|
+
return disposer
|
|
190
|
+
}
|