mirror of
https://github.com/xmanrui/OpenClaw-bot-review.git
synced 2026-07-31 04:06:49 +00:00
- Add layout editor with floor/wall paint, erase, furniture place/move/rotate/delete tools - Add editor state management (undo/redo, selection, drag, ghost preview) - Add EditorToolbar and EditActionBar UI components - Add layout persistence API (GET/POST to ~/.openclaw/pixel-office/layout.json) - Add notification sound system (Web Audio API ascending chime on agent completion) - Add sub-agent visualization via session JSONL parsing - Fix agent activity API to read from agents.list config and correct session paths - Add agent name labels above character sprites in canvas renderer - Add i18n translations for editor features (zh/en)
67 lines
1.6 KiB
TypeScript
67 lines
1.6 KiB
TypeScript
import {
|
|
NOTIFICATION_NOTE_1_HZ,
|
|
NOTIFICATION_NOTE_2_HZ,
|
|
NOTIFICATION_NOTE_1_START_SEC,
|
|
NOTIFICATION_NOTE_2_START_SEC,
|
|
NOTIFICATION_NOTE_DURATION_SEC,
|
|
NOTIFICATION_VOLUME,
|
|
} from './constants'
|
|
|
|
let soundEnabled = true
|
|
let audioCtx: AudioContext | null = null
|
|
|
|
export function setSoundEnabled(enabled: boolean): void {
|
|
soundEnabled = enabled
|
|
}
|
|
|
|
export function isSoundEnabled(): boolean {
|
|
return soundEnabled
|
|
}
|
|
|
|
function playNote(ctx: AudioContext, freq: number, startOffset: number): void {
|
|
const t = ctx.currentTime + startOffset
|
|
const osc = ctx.createOscillator()
|
|
const gain = ctx.createGain()
|
|
|
|
osc.type = 'sine'
|
|
osc.frequency.setValueAtTime(freq, t)
|
|
|
|
gain.gain.setValueAtTime(NOTIFICATION_VOLUME, t)
|
|
gain.gain.exponentialRampToValueAtTime(0.001, t + NOTIFICATION_NOTE_DURATION_SEC)
|
|
|
|
osc.connect(gain)
|
|
gain.connect(ctx.destination)
|
|
|
|
osc.start(t)
|
|
osc.stop(t + NOTIFICATION_NOTE_DURATION_SEC)
|
|
}
|
|
|
|
export async function playDoneSound(): Promise<void> {
|
|
if (!soundEnabled) return
|
|
try {
|
|
if (!audioCtx) {
|
|
audioCtx = new AudioContext()
|
|
}
|
|
if (audioCtx.state === 'suspended') {
|
|
await audioCtx.resume()
|
|
}
|
|
playNote(audioCtx, NOTIFICATION_NOTE_1_HZ, NOTIFICATION_NOTE_1_START_SEC)
|
|
playNote(audioCtx, NOTIFICATION_NOTE_2_HZ, NOTIFICATION_NOTE_2_START_SEC)
|
|
} catch {
|
|
// Audio may not be available
|
|
}
|
|
}
|
|
|
|
export function unlockAudio(): void {
|
|
try {
|
|
if (!audioCtx) {
|
|
audioCtx = new AudioContext()
|
|
}
|
|
if (audioCtx.state === 'suspended') {
|
|
audioCtx.resume()
|
|
}
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|