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:
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user