feat: AI Generation panel integrated into openDAW

AIPanel component with 6 buttons: MUSIC, VOICE, DRONE, PINK, WHITE, SUGGEST
Connects to AI Bridge at :8301 for generation
Audio playback inline
SASS styling matching openDAW dark theme

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kxkm
2026-03-21 12:47:18 +01:00
parent 1351fccb30
commit 6203b36dd0
3 changed files with 166 additions and 1 deletions
+3 -1
View File
@@ -19,6 +19,7 @@ import {UsersPage} from "@/ui/pages/UsersPage"
import {PrivacyPage} from "@/ui/pages/PrivacyPage"
import {PreferencesPage} from "@/ui/pages/PreferencesPage"
import {TestPage} from "@/ui/pages/TestPage"
import {AIPanel} from "@/ui/ai-panel/AIPanel"
export const App = (service: StudioService) => {
const terminator = new Terminator()
@@ -54,6 +55,7 @@ export const App = (service: StudioService) => {
]}
/>
<Footer lifecycle={terminator} service={service}/>
<AIPanel />
</Frag>
)
}
}
@@ -0,0 +1,68 @@
.AIPanel
background: #1a1a1a
border-top: 1px solid #333
padding: 8px 12px
font-family: "SF Mono", monospace
font-size: 11px
.ai-header
color: #4a9eff
font-weight: bold
margin-bottom: 6px
font-size: 12px
.ai-prompt
width: 100%
background: #242424
border: 1px solid #444
color: #ddd
font-family: inherit
font-size: 11px
padding: 6px 8px
border-radius: 3px
resize: none
box-sizing: border-box
&:focus
outline: none
border-color: #4a9eff
.ai-buttons
display: flex
gap: 4px
margin: 6px 0
flex-wrap: wrap
.ai-btn
background: #333
border: 1px solid #555
color: #bbb
font-family: inherit
font-size: 10px
padding: 4px 10px
border-radius: 3px
cursor: pointer
transition: all 0.1s
&:hover
background: #444
color: #fff
&:active
transform: scale(0.97)
.ai-btn-accent
background: #2a5a99
border-color: #4a9eff
color: #ddd
&:hover
background: #4a9eff
color: #fff
.ai-status
color: #888
font-size: 10px
min-height: 14px
margin: 4px 0
.ai-audio
width: 100%
height: 28px
margin-top: 4px
@@ -0,0 +1,95 @@
import css from "./AIPanel.sass?inline"
import {createElement} from "@opendaw/lib-jsx"
import {Html} from "@opendaw/lib-dom"
const className = Html.adoptStyleSheet(css, "AIPanel")
const AI_BRIDGE = "http://localhost:8301"
type Construct = {}
export const AIPanel = ({}: Construct) => {
let promptInput: HTMLTextAreaElement
let statusEl: HTMLElement
let resultAudio: HTMLAudioElement
async function generateMusic() {
const prompt = promptInput.value.trim()
if (!prompt) return
statusEl.textContent = "Generating music..."
try {
const resp = await fetch(`${AI_BRIDGE}/generate/music`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({prompt, duration: 30, style: "experimental"})
})
if (!resp.ok) throw new Error(`${resp.status}`)
const blob = await resp.blob()
resultAudio.src = URL.createObjectURL(blob)
statusEl.textContent = "Music generated!"
} catch(e) { statusEl.textContent = `Error: ${e}` }
}
async function generateVoice() {
const text = promptInput.value.trim()
if (!text) return
statusEl.textContent = "Generating voice..."
try {
const resp = await fetch(`${AI_BRIDGE}/generate/voice`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({text, persona: "pharmacius"})
})
if (!resp.ok) throw new Error(`${resp.status}`)
const blob = await resp.blob()
resultAudio.src = URL.createObjectURL(blob)
statusEl.textContent = "Voice generated!"
} catch(e) { statusEl.textContent = `Error: ${e}` }
}
async function generateNoise(type: string) {
statusEl.textContent = `Generating ${type}...`
try {
const resp = await fetch(`${AI_BRIDGE}/generate/noise`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({type, duration: 10})
})
if (!resp.ok) throw new Error(`${resp.status}`)
const blob = await resp.blob()
resultAudio.src = URL.createObjectURL(blob)
statusEl.textContent = `${type} noise generated!`
} catch(e) { statusEl.textContent = `Error: ${e}` }
}
async function suggest() {
statusEl.textContent = "AI thinking..."
try {
const resp = await fetch(`${AI_BRIDGE}/generate/suggest`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({tracks: []})
})
const data = await resp.json()
promptInput.value = typeof data === "string" ? data : JSON.stringify(data)
statusEl.textContent = "Suggestion ready"
} catch(e) { statusEl.textContent = `Error: ${e}` }
}
return (
<div className={className}>
<div className="ai-header">AI GENERATION</div>
<textarea onInit={(el: HTMLTextAreaElement) => promptInput = el} className="ai-prompt" placeholder="Describe your sound..." rows={2} />
<div className="ai-buttons">
<button className="ai-btn ai-btn-accent" onclick={generateMusic}>MUSIC</button>
<button className="ai-btn" onclick={generateVoice}>VOICE</button>
<button className="ai-btn" onclick={() => generateNoise("drone")}>DRONE</button>
<button className="ai-btn" onclick={() => generateNoise("pink")}>PINK</button>
<button className="ai-btn" onclick={() => generateNoise("white")}>WHITE</button>
<button className="ai-btn" onclick={suggest}>SUGGEST</button>
</div>
<div onInit={(el: HTMLElement) => statusEl = el} className="ai-status"></div>
<audio onInit={(el: HTMLAudioElement) => resultAudio = el} controls className="ai-audio" />
</div>
)
}