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
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar entry injection.
|
|
3
|
+
*
|
|
4
|
+
* dsh's sidebar shell exposes no slot an external plugin can register into,
|
|
5
|
+
* so the entry row is injected at the DOM level after the shell's New Session
|
|
6
|
+
* button. A MutationObserver self-heals when React re-renders.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { AudioGenController } from './controller.ts'
|
|
10
|
+
import css from './panel.module.css'
|
|
11
|
+
|
|
12
|
+
export const ENTRY_SELECTOR = '[data-dsh-audiogen-entry]'
|
|
13
|
+
|
|
14
|
+
const ICON = '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 3.5h10v9H3z"/><path d="M1.5 5.5v5"/><path d="M14.5 5.5v5"/><path d="M6 6.5l4 1.5-4 1.5z"/></svg>'
|
|
15
|
+
|
|
16
|
+
const FAMILY_ENTRY_SELECTOR = '[data-dsh-taskboard-entry], [data-dsh-ssh-entry], [data-dsh-imagegen-entry], [data-dsh-audiogen-entry]'
|
|
17
|
+
|
|
18
|
+
function sidebarRoot(): HTMLElement | undefined {
|
|
19
|
+
const column = document.querySelector<HTMLElement>('[data-pane="sidebar"], [class*="sidebarCol"]')
|
|
20
|
+
if (column === null) return undefined
|
|
21
|
+
const logoOwner = column.querySelector<HTMLElement>('[class*="logoRow"]')?.parentElement
|
|
22
|
+
return logoOwner ?? (column.firstElementChild as HTMLElement | undefined)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function newSessionButton(root: HTMLElement): HTMLButtonElement | undefined {
|
|
26
|
+
const nested = root.querySelector<HTMLButtonElement>('button[class*="newSession"]')
|
|
27
|
+
if (nested !== null) return nested
|
|
28
|
+
for (const child of root.children) {
|
|
29
|
+
if (child.tagName === 'BUTTON') return child as HTMLButtonElement
|
|
30
|
+
}
|
|
31
|
+
return undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function createEntry(controller: AudioGenController, label: string, tooltip: string): HTMLButtonElement {
|
|
35
|
+
const entry = document.createElement('button')
|
|
36
|
+
entry.type = 'button'
|
|
37
|
+
entry.dataset.dshAudiogenEntry = ''
|
|
38
|
+
entry.className = css.entry
|
|
39
|
+
entry.setAttribute('aria-label', label)
|
|
40
|
+
entry.setAttribute('title', tooltip)
|
|
41
|
+
entry.innerHTML = '<span class="' + css.entryIcon + '">' + ICON + '</span><span class="' + css.entryLabel + '">' + label + '</span>'
|
|
42
|
+
entry.addEventListener('click', () => { controller.toggle() })
|
|
43
|
+
return entry
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function placeEntry(root: HTMLElement, entry: HTMLButtonElement): boolean {
|
|
47
|
+
const button = newSessionButton(root)
|
|
48
|
+
if (button === undefined) return false
|
|
49
|
+
if (entry.parentElement !== root) {
|
|
50
|
+
const row = button.closest('[class*="logoRow"]')
|
|
51
|
+
const base = (row !== null && row.parentElement === root) ? row : button
|
|
52
|
+
const family = Array.from(root.children).filter(
|
|
53
|
+
(el): el is HTMLElement => el instanceof HTMLElement && el.matches(FAMILY_ENTRY_SELECTOR),
|
|
54
|
+
)
|
|
55
|
+
const anchor = family.length > 0 ? family[family.length - 1].nextElementSibling : base.nextElementSibling
|
|
56
|
+
root.insertBefore(entry, anchor)
|
|
57
|
+
}
|
|
58
|
+
return true
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function mountSidebarEntry(controller: AudioGenController, label: string, tooltip: string): () => void {
|
|
62
|
+
const entry = createEntry(controller, label, tooltip)
|
|
63
|
+
let root: HTMLElement | undefined
|
|
64
|
+
let placed = false
|
|
65
|
+
|
|
66
|
+
const tryPlace = (): void => {
|
|
67
|
+
if (root !== undefined && !root.isConnected) {
|
|
68
|
+
rootObserver.disconnect()
|
|
69
|
+
root = undefined
|
|
70
|
+
placed = false
|
|
71
|
+
}
|
|
72
|
+
if (placed) {
|
|
73
|
+
if (document.body.contains(entry)) return
|
|
74
|
+
rootObserver.disconnect()
|
|
75
|
+
root = undefined
|
|
76
|
+
placed = false
|
|
77
|
+
}
|
|
78
|
+
root ??= sidebarRoot()
|
|
79
|
+
if (root === undefined) return
|
|
80
|
+
placed = placeEntry(root, entry)
|
|
81
|
+
if (placed) {
|
|
82
|
+
rootObserver.observe(root, { childList: true, subtree: true })
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const waitObserver = new MutationObserver(() => { tryPlace() })
|
|
87
|
+
waitObserver.observe(document.body, { childList: true, subtree: true })
|
|
88
|
+
|
|
89
|
+
const rootObserver = new MutationObserver(() => {
|
|
90
|
+
if (root === undefined || !root.isConnected) {
|
|
91
|
+
placed = false
|
|
92
|
+
tryPlace()
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
if (!root.contains(entry)) {
|
|
96
|
+
placed = placeEntry(root, entry)
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const syncActive = () => {
|
|
101
|
+
if (controller.getSnapshot().panelOpen) entry.dataset.active = 'true'
|
|
102
|
+
else delete entry.dataset.active
|
|
103
|
+
}
|
|
104
|
+
const unsubscribe = controller.subscribe(syncActive)
|
|
105
|
+
syncActive()
|
|
106
|
+
|
|
107
|
+
tryPlace()
|
|
108
|
+
|
|
109
|
+
return () => {
|
|
110
|
+
waitObserver.disconnect()
|
|
111
|
+
rootObserver.disconnect()
|
|
112
|
+
unsubscribe()
|
|
113
|
+
entry.remove()
|
|
114
|
+
}
|
|
115
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-audiogen — host half.
|
|
3
|
+
*
|
|
4
|
+
* Mounts the plugin settings section (multi-provider audio channels), the
|
|
5
|
+
* /api/dsh-audiogen route family (settings bridge, presets, generation proxy,
|
|
6
|
+
* audio/history serving), and the Agent audio tool. The browser half
|
|
7
|
+
* (./client) renders the sidebar entry and the audio generation panel.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
11
|
+
import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings'
|
|
12
|
+
import z from 'schemastery'
|
|
13
|
+
import type {} from '@deepseek-ai/dsh-host-webserver'
|
|
14
|
+
import type {} from '@deepseek-ai/dsh-system-prompt'
|
|
15
|
+
import type {} from '@deepseek-ai/dsh-tools'
|
|
16
|
+
import { AUDIOGEN_SETTINGS_NAMESPACE, type ChannelConfig, type ModelMapping } from './protocol.ts'
|
|
17
|
+
import { makeRoutes, type ChannelsView, type SettingsSeam } from './routes.ts'
|
|
18
|
+
import type { AudioChannel } from './audio-engine.ts'
|
|
19
|
+
import { registerAgentAudioTools, type AgentAudioToolConfig } from './agent-audio-tools.ts'
|
|
20
|
+
import { audioPresetById } from './audio-presets.ts'
|
|
21
|
+
|
|
22
|
+
/** Stable cordis plugin name. */
|
|
23
|
+
export const name = 'audiogen'
|
|
24
|
+
|
|
25
|
+
/** Services required before the surfaces can mount. */
|
|
26
|
+
export const inject = ['webServer', 'systemPrompt']
|
|
27
|
+
|
|
28
|
+
/** The branded settings namespace of this plugin. */
|
|
29
|
+
export const AudioGenSettingsNamespace = settingsNamespace(AUDIOGEN_SETTINGS_NAMESPACE)
|
|
30
|
+
|
|
31
|
+
export interface Config {
|
|
32
|
+
enabled?: boolean
|
|
33
|
+
announceToAgent?: boolean
|
|
34
|
+
allowAgentAudioGeneration?: boolean
|
|
35
|
+
channels?: ChannelConfig[]
|
|
36
|
+
channelSecrets?: Record<string, string>
|
|
37
|
+
defaultChannelId?: string
|
|
38
|
+
defaultModel?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const Config: z<Config> = z.object({
|
|
42
|
+
enabled: z.boolean().default(true),
|
|
43
|
+
announceToAgent: z.boolean().default(true),
|
|
44
|
+
allowAgentAudioGeneration: z.boolean().default(true),
|
|
45
|
+
channels: z.array(z.object({
|
|
46
|
+
id: z.string(),
|
|
47
|
+
preset: z.string().default(''),
|
|
48
|
+
name: z.string().default(''),
|
|
49
|
+
apiUrl: z.string().default(''),
|
|
50
|
+
models: z.array(z.object({
|
|
51
|
+
alias: z.string(),
|
|
52
|
+
id: z.string(),
|
|
53
|
+
})).default([]),
|
|
54
|
+
})).default([]),
|
|
55
|
+
channelSecrets: z.dict(z.string().role('secret')).default({}),
|
|
56
|
+
defaultChannelId: z.string().default(''),
|
|
57
|
+
defaultModel: z.string().default(''),
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const DEFAULT_ENABLED = true
|
|
61
|
+
const DEFAULT_ANNOUNCE = true
|
|
62
|
+
const DEFAULT_ALLOW_AGENT_AUDIO = true
|
|
63
|
+
|
|
64
|
+
const SECTION_ORDER = 160
|
|
65
|
+
|
|
66
|
+
export const AUDIOGEN_GUIDANCE = '本机已安装 dsh-audiogen 插件(DSH AI 音频):侧边栏「AI 音频」入口。能力:通过「渠道」对接多个音频生成厂商(OpenAI TTS、ElevenLabs、MiniMax、Stability Audio、自定义 OpenAI 兼容接口),支持 TTS 文本转语音、音乐生成和音效生成。API 地址与密钥在 GUI 设置中按渠道配置,密钥仅存于本机设置文档;生成请求由本地宿主代理转发。Agent 可直接调用 `generate_audio` 提交 TTS/音乐/音效任务,默认等待完成并返回同源音频 URL。限制:生成消耗上游 API 额度;音频内容由上游模型生成;模型只能使用用户在各渠道配置目录中的模型。用户提到「音频 / 语音 / TTS / 配乐 / 音效 / AI 音频」时即指本插件,请据此协作。'
|
|
67
|
+
|
|
68
|
+
function guidanceFor(channels: AudioChannel[], defaultChannelId: string): string {
|
|
69
|
+
if (channels.length === 0) {
|
|
70
|
+
return `${AUDIOGEN_GUIDANCE} 尚未配置任何渠道:请先在「设置 → 插件 → AI 音频」添加渠道并填写 API 地址与密钥。`
|
|
71
|
+
}
|
|
72
|
+
const table = channels.map(channel => {
|
|
73
|
+
const aliases = channel.models.map(model => model.alias).join('、')
|
|
74
|
+
const mark = channel.id === defaultChannelId ? '(默认渠道)' : ''
|
|
75
|
+
const key = channel.apiKey === '' ? '(未填密钥)' : ''
|
|
76
|
+
const models = channel.models.length === 0 ? '未配置模型/音色' : `可用模型/音色:${aliases}`
|
|
77
|
+
return `渠道「${channel.name}」${mark}[${channel.apiUrl}] ${models}${key}`
|
|
78
|
+
}).join(';')
|
|
79
|
+
return `${AUDIOGEN_GUIDANCE} 当前渠道与模型:${table}。`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function normalizeChannels(value: unknown): ChannelConfig[] {
|
|
83
|
+
if (!Array.isArray(value)) return []
|
|
84
|
+
const out: ChannelConfig[] = []
|
|
85
|
+
for (const item of value) {
|
|
86
|
+
if (item === null || typeof item !== 'object') continue
|
|
87
|
+
const raw = item as Record<string, unknown>
|
|
88
|
+
const id = typeof raw.id === 'string' ? raw.id.trim() : ''
|
|
89
|
+
if (id === '') continue
|
|
90
|
+
const models: ModelMapping[] = []
|
|
91
|
+
if (Array.isArray(raw.models)) {
|
|
92
|
+
for (const entry of raw.models) {
|
|
93
|
+
if (entry === null || typeof entry !== 'object') continue
|
|
94
|
+
const record = entry as Record<string, unknown>
|
|
95
|
+
const alias = typeof record.alias === 'string' ? record.alias.trim() : ''
|
|
96
|
+
const upstream = typeof record.id === 'string' ? record.id.trim() : ''
|
|
97
|
+
if (alias === '') continue
|
|
98
|
+
models.push({ alias, id: upstream === '' ? alias : upstream })
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
out.push({
|
|
102
|
+
id,
|
|
103
|
+
preset: typeof raw.preset === 'string' ? raw.preset : '',
|
|
104
|
+
name: typeof raw.name === 'string' ? raw.name.trim() : '',
|
|
105
|
+
apiUrl: typeof raw.apiUrl === 'string' ? raw.apiUrl.trim() : '',
|
|
106
|
+
models,
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
return out
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface EffectiveConfig {
|
|
113
|
+
enabled: boolean
|
|
114
|
+
announceToAgent: boolean
|
|
115
|
+
allowAgentAudioGeneration: boolean
|
|
116
|
+
channels: AudioChannel[]
|
|
117
|
+
defaultChannelId: string
|
|
118
|
+
defaultModel: string
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function apply(ctx: Context, config?: Config): void {
|
|
122
|
+
let current: () => Config = () => config ?? {}
|
|
123
|
+
|
|
124
|
+
const resolve = (): EffectiveConfig => {
|
|
125
|
+
const value = current() ?? {}
|
|
126
|
+
const channels = normalizeChannels(value.channels)
|
|
127
|
+
const secrets: Record<string, string> = { ...(value.channelSecrets ?? {}) }
|
|
128
|
+
const named = channels.map(channel => ({
|
|
129
|
+
...channel,
|
|
130
|
+
name: channel.name === '' ? (audioPresetById(channel.preset)?.name ?? '未命名渠道') : channel.name,
|
|
131
|
+
}))
|
|
132
|
+
const defaultChannelId = typeof value.defaultChannelId === 'string' && named.some(channel => channel.id === value.defaultChannelId)
|
|
133
|
+
? value.defaultChannelId
|
|
134
|
+
: named[0]?.id ?? ''
|
|
135
|
+
return {
|
|
136
|
+
enabled: value.enabled ?? DEFAULT_ENABLED,
|
|
137
|
+
announceToAgent: value.announceToAgent ?? DEFAULT_ANNOUNCE,
|
|
138
|
+
allowAgentAudioGeneration: value.allowAgentAudioGeneration ?? DEFAULT_ALLOW_AGENT_AUDIO,
|
|
139
|
+
channels: named.map(channel => ({
|
|
140
|
+
...channel,
|
|
141
|
+
apiKey: typeof secrets[channel.id] === 'string' ? secrets[channel.id] : '',
|
|
142
|
+
})),
|
|
143
|
+
defaultChannelId,
|
|
144
|
+
defaultModel: typeof value.defaultModel === 'string' ? value.defaultModel.trim() : '',
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const channelsView = (): ChannelsView => {
|
|
149
|
+
const value = resolve()
|
|
150
|
+
return { channels: value.channels, defaultChannelId: value.defaultChannelId }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
ctx.inject(['settings', 'webServer'], (sctx) => {
|
|
154
|
+
const seam = sctx.get('settings') as unknown as SettingsSeam
|
|
155
|
+
sctx.effect(() => {
|
|
156
|
+
const routes = makeRoutes({
|
|
157
|
+
settings: seam,
|
|
158
|
+
resolveChannels: channelsView,
|
|
159
|
+
})
|
|
160
|
+
const disposers = routes.map(route => ctx.webServer.register(route))
|
|
161
|
+
return () => { for (const dispose of disposers) dispose() }
|
|
162
|
+
}, 'dsh-audiogen: routes')
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
ctx.inject(['tools'], (tctx) => {
|
|
166
|
+
tctx.effect(() => registerAgentAudioTools(tctx, (): AgentAudioToolConfig => {
|
|
167
|
+
const value = resolve()
|
|
168
|
+
return {
|
|
169
|
+
enabled: value.enabled,
|
|
170
|
+
allowAgentAudioGeneration: value.allowAgentAudioGeneration,
|
|
171
|
+
channels: value.channels,
|
|
172
|
+
defaultChannelId: value.defaultChannelId,
|
|
173
|
+
}
|
|
174
|
+
}), 'dsh-audiogen: agent audio tools')
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
let disposeSection: (() => void) | undefined
|
|
178
|
+
const sync = (): void => {
|
|
179
|
+
if (disposeSection !== undefined) {
|
|
180
|
+
disposeSection()
|
|
181
|
+
disposeSection = undefined
|
|
182
|
+
}
|
|
183
|
+
const value = resolve()
|
|
184
|
+
if (!value.enabled || !value.announceToAgent) return
|
|
185
|
+
disposeSection = ctx.systemPrompt.section({
|
|
186
|
+
name: 'plugin:dsh-audiogen',
|
|
187
|
+
order: SECTION_ORDER,
|
|
188
|
+
text: guidanceFor(value.channels, value.defaultChannelId),
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
installSettingsSection(ctx, AudioGenSettingsNamespace, Config, config ?? {}, {
|
|
193
|
+
setSource: (source) => {
|
|
194
|
+
current = source
|
|
195
|
+
sync()
|
|
196
|
+
},
|
|
197
|
+
onChange: sync,
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
sync()
|
|
201
|
+
}
|
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire contract shared by the host and client halves of dsh-audiogen:
|
|
3
|
+
* settings namespace, route paths, generate payload/result shapes.
|
|
4
|
+
* Pure types and constants — safe for the client bundle to inline.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Settings namespace this plugin owns (host settings seam + bridge). */
|
|
8
|
+
export const AUDIOGEN_SETTINGS_NAMESPACE = 'dsh-audiogen'
|
|
9
|
+
|
|
10
|
+
/** Published package version shared by the host updater and the client UI. */
|
|
11
|
+
export const PLUGIN_VERSION = '0.1.0'
|
|
12
|
+
|
|
13
|
+
/** Same-origin route family (loopback-only, mirroring dsh-imagegen). */
|
|
14
|
+
export const SETTINGS_API = {
|
|
15
|
+
describe: '/api/dsh-audiogen/settings/describe',
|
|
16
|
+
mutate: '/api/dsh-audiogen/settings/mutate',
|
|
17
|
+
} as const
|
|
18
|
+
|
|
19
|
+
/** The audio-generation proxy route. */
|
|
20
|
+
export const GENERATE_API = '/api/dsh-audiogen/generate' as const
|
|
21
|
+
|
|
22
|
+
/** Host-mediated built-in provider catalog (channels the user can instantiate). */
|
|
23
|
+
export const PRESETS_API = '/api/dsh-audiogen/presets' as const
|
|
24
|
+
|
|
25
|
+
/** Loopback-only audio file reader for panel/tool-result previews. */
|
|
26
|
+
export const AUDIO_API = {
|
|
27
|
+
file: '/api/dsh-audiogen/audio',
|
|
28
|
+
} as const
|
|
29
|
+
|
|
30
|
+
/** Host-persisted generation history routes. */
|
|
31
|
+
export const HISTORY_API = {
|
|
32
|
+
list: '/api/dsh-audiogen/history/list',
|
|
33
|
+
append: '/api/dsh-audiogen/history/append',
|
|
34
|
+
remove: '/api/dsh-audiogen/history/remove',
|
|
35
|
+
clear: '/api/dsh-audiogen/history/clear',
|
|
36
|
+
audio: '/api/dsh-audiogen/history/audio',
|
|
37
|
+
} as const
|
|
38
|
+
|
|
39
|
+
/** Maximum number of history entries retained host-side (oldest evicted). */
|
|
40
|
+
export const HISTORY_MAX = 50
|
|
41
|
+
|
|
42
|
+
/** Audio generation modes. */
|
|
43
|
+
export type AudioMode = 'tts' | 'music' | 'sfx'
|
|
44
|
+
|
|
45
|
+
/** One model mapping in a channel's catalog: display alias → upstream id. */
|
|
46
|
+
export interface ModelMapping {
|
|
47
|
+
/** User-facing model/voice name (defaults to the upstream id). */
|
|
48
|
+
alias: string
|
|
49
|
+
/** Upstream model or voice id sent to the provider. */
|
|
50
|
+
id: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* One configured audio channel (provider). Secrets never live here — the API
|
|
55
|
+
* key is stored at `channelSecrets.<channelId>`.
|
|
56
|
+
*/
|
|
57
|
+
export interface ChannelConfig {
|
|
58
|
+
/** Stable channel id (the channelSecrets dict is keyed by it). */
|
|
59
|
+
id: string
|
|
60
|
+
/** Preset provider id this channel was created from ('' = custom). */
|
|
61
|
+
preset: string
|
|
62
|
+
/** Display name shown in the list, panel, and Agent guidance. */
|
|
63
|
+
name: string
|
|
64
|
+
/** Provider base URL. */
|
|
65
|
+
apiUrl: string
|
|
66
|
+
/** The channel's model/voice catalog (alias → upstream id). */
|
|
67
|
+
models: ModelMapping[]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** One built-in provider as the settings card consumes it. */
|
|
71
|
+
export interface PresetProviderView {
|
|
72
|
+
id: string
|
|
73
|
+
name: string
|
|
74
|
+
apiUrl: string
|
|
75
|
+
hint: string
|
|
76
|
+
models: ModelMapping[]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** A client → host generate request. */
|
|
80
|
+
export interface GenerateAudioRequest {
|
|
81
|
+
mode: AudioMode
|
|
82
|
+
/** User-facing model/voice alias; host maps to upstream id. */
|
|
83
|
+
model: string
|
|
84
|
+
/** TTS text or music/sfx prompt. */
|
|
85
|
+
prompt: string
|
|
86
|
+
/** Optional voice alias for TTS. */
|
|
87
|
+
voice?: string
|
|
88
|
+
/** Optional speaking rate / speed multiplier. */
|
|
89
|
+
speed?: number
|
|
90
|
+
/** Requested duration in seconds (music/sfx). */
|
|
91
|
+
duration?: number
|
|
92
|
+
/** Output format, e.g. mp3, wav, pcm. */
|
|
93
|
+
format?: string
|
|
94
|
+
/** Channel this request targets (host falls back to default). */
|
|
95
|
+
channelId?: string
|
|
96
|
+
/** Channel display name snapshot (host-filled). */
|
|
97
|
+
channel?: string
|
|
98
|
+
/** Upstream model id actually sent (host-filled from alias mapping). */
|
|
99
|
+
upstream?: string
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** One generated audio, normalized host-side to base64. */
|
|
103
|
+
export interface GeneratedAudio {
|
|
104
|
+
/** Raw base64 payload (no data: prefix). */
|
|
105
|
+
b64: string
|
|
106
|
+
/** MIME type, e.g. audio/mpeg. */
|
|
107
|
+
mime: string
|
|
108
|
+
/** Exact encoded byte length. */
|
|
109
|
+
bytes: number
|
|
110
|
+
/** Optional duration in seconds when the API reports one. */
|
|
111
|
+
duration?: number
|
|
112
|
+
/** Same-origin URL served by the host ('' when unavailable). */
|
|
113
|
+
url: string
|
|
114
|
+
/** Stable audio id / file name. */
|
|
115
|
+
id: string
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Successful generate outcome. */
|
|
119
|
+
export interface GenerateAudioResult {
|
|
120
|
+
outputs: GeneratedAudio[]
|
|
121
|
+
/** Updated host-persisted history, when returned. */
|
|
122
|
+
history?: HistoryEntry[]
|
|
123
|
+
/** Persistence failure after audio was generated. */
|
|
124
|
+
historyError?: string
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** One audio reference as the browser consumes it. */
|
|
128
|
+
export interface HistoryAudioRef {
|
|
129
|
+
/** Same-origin URL. */
|
|
130
|
+
url: string
|
|
131
|
+
/** MIME type, e.g. audio/mpeg. */
|
|
132
|
+
mime: string
|
|
133
|
+
/** Duration in seconds when known. */
|
|
134
|
+
duration?: number
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** A saved generation as the browser consumes it. */
|
|
138
|
+
export interface HistoryEntry {
|
|
139
|
+
id: string
|
|
140
|
+
createdAt: number
|
|
141
|
+
mode: AudioMode
|
|
142
|
+
model: string
|
|
143
|
+
prompt: string
|
|
144
|
+
voice?: string
|
|
145
|
+
speed?: number
|
|
146
|
+
duration?: number
|
|
147
|
+
format?: string
|
|
148
|
+
audio: HistoryAudioRef[]
|
|
149
|
+
channelId?: string
|
|
150
|
+
channel?: string
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** A history entry the client submits for persistence (audio still carries base64). */
|
|
154
|
+
export interface HistoryEntryInput {
|
|
155
|
+
id: string
|
|
156
|
+
createdAt: number
|
|
157
|
+
mode: AudioMode
|
|
158
|
+
model: string
|
|
159
|
+
prompt: string
|
|
160
|
+
voice?: string
|
|
161
|
+
speed?: number
|
|
162
|
+
duration?: number
|
|
163
|
+
format?: string
|
|
164
|
+
audio: GeneratedAudio[]
|
|
165
|
+
channelId?: string
|
|
166
|
+
channel?: string
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** The plugin settings fields edited by the settings card and panel. */
|
|
170
|
+
export interface AudiogenConfig {
|
|
171
|
+
enabled?: boolean
|
|
172
|
+
announceToAgent?: boolean
|
|
173
|
+
allowAgentAudioGeneration?: boolean
|
|
174
|
+
channels?: ChannelConfig[]
|
|
175
|
+
channelSecrets?: Record<string, string>
|
|
176
|
+
defaultChannelId?: string
|
|
177
|
+
/** Optional default voice/model alias for quick generation. */
|
|
178
|
+
defaultModel?: string
|
|
179
|
+
}
|