feat: add official game audio generation client
This commit is contained in:
@@ -82,9 +82,10 @@ async function installCodingFirstChatHost(
|
||||
hostConnection: HostConnection,
|
||||
featureComplete = false,
|
||||
managedCapabilities = false,
|
||||
audioPreview?: { executionId: string; path: string; dataUrl: string },
|
||||
): Promise<void> {
|
||||
await electronApp.evaluate(async (_, payload) => {
|
||||
const { connection, featureComplete, managedCapabilities } = payload;
|
||||
const { connection, featureComplete, managedCapabilities, audioPreview } = payload;
|
||||
const { ipcMain } = process.mainModule!.require('electron') as typeof import('electron');
|
||||
type MainState = {
|
||||
captured: CapturedRequest[];
|
||||
@@ -456,6 +457,9 @@ async function installCodingFirstChatHost(
|
||||
) => {
|
||||
const path = request.path ?? '';
|
||||
const method = request.method ?? 'GET';
|
||||
if (audioPreview && path === '/api/coding/game-audio/' + audioPreview.executionId + '/outputs/0') {
|
||||
return respond({ path: audioPreview.path, dataUrl: audioPreview.dataUrl });
|
||||
}
|
||||
const body = typeof request.body === 'string' && request.body
|
||||
? JSON.parse(request.body) as Record<string, unknown>
|
||||
: undefined;
|
||||
@@ -737,9 +741,71 @@ async function installCodingFirstChatHost(
|
||||
if (path === '/api/coding/runtime/diagnostics') return respond({ runtime: { revision: { provider: 1, resources: 1 }, workers: [{ conversationId: conversation.id, generation: 1, state: 'running', stage: 'running' }] } });
|
||||
return respond({ success: false, error: `Unhandled E2E route: ${method} ${path}` }, 404);
|
||||
});
|
||||
}, { connection: hostConnection, featureComplete, managedCapabilities });
|
||||
}, { connection: hostConnection, featureComplete, managedCapabilities, audioPreview });
|
||||
}
|
||||
|
||||
test('saved Game Audio offers click-only playable local preview in Electron', async ({ launchElectronApp }, testInfo) => {
|
||||
const app = await launchElectronApp({ skipSetup: true });
|
||||
let page = await getStableWindow(app);
|
||||
const connection = await page.evaluate(async () => ({
|
||||
token: await window.electron.ipcRenderer.invoke('hostapi:token') as string,
|
||||
baseUrl: await window.electron.ipcRenderer.invoke('hostapi:base-url') as string,
|
||||
}));
|
||||
const executionId = '00000000-0000-4000-8000-000000000601';
|
||||
const relative = 'assets/generated/game-audio/' + executionId + '/sound-1.wav';
|
||||
// Synthetic quarter-second PCM sample, never a claimed Meowa output.
|
||||
const wav = Buffer.alloc(4044);
|
||||
wav.write('RIFF', 0); wav.writeUInt32LE(4036, 4); wav.write('WAVEfmt ', 8);
|
||||
wav.writeUInt32LE(16, 16); wav.writeUInt16LE(1, 20); wav.writeUInt16LE(1, 22);
|
||||
wav.writeUInt32LE(8000, 24); wav.writeUInt32LE(16000, 28);
|
||||
wav.writeUInt16LE(2, 32); wav.writeUInt16LE(16, 34);
|
||||
wav.write('data', 36); wav.writeUInt32LE(4000, 40);
|
||||
await installCodingFirstChatHost(app, connection, true, false, {
|
||||
executionId, path: relative, dataUrl: 'data:audio/wav;base64,' + wav.toString('base64'),
|
||||
});
|
||||
await settleSnapshot(app);
|
||||
await disableCodingEventSource(page);
|
||||
await page.reload();
|
||||
page = await getStableWindow(app);
|
||||
await page.getByTestId('ai-module-option-programming').click();
|
||||
await page.evaluate(() => { window.location.hash = '/chat'; });
|
||||
await expect(page.getByTestId('coding-conversation-header')).toBeVisible();
|
||||
const snapshot = await page.evaluate(async () => {
|
||||
const response = await window.electron.ipcRenderer.invoke('hostapi:fetch', {
|
||||
path: '/api/coding/conversations/conversation-pi-first-chat/snapshot', method: 'GET',
|
||||
}) as { data: { json: { snapshot: Record<string, unknown> } } };
|
||||
return response.data.json.snapshot;
|
||||
});
|
||||
const details = {
|
||||
schema: 'makelore-capability.v1', plugin_id: 'makelore.game-audio', plugin_version: '1.0.0',
|
||||
capability_id: 'game-audio.sound', operation: 'generate', request_id: 'pi:r:t',
|
||||
success: true, status: 200, code: null, error: null, retryable: false, payload_schema: 'game-audio.v1',
|
||||
billing: { mode: 'platform_metered', status: 'settled', reserved_points: '0.04',
|
||||
actual_points: '0.04', usage_amount: 4, unit: 'half_second' },
|
||||
data: { executionId, logicalOperationId: 'pi:r:t', projectId: 'project-e2e',
|
||||
providerStatus: 'succeeded', deliveryStatus: 'saved', phase: 'saved', outputCount: 1,
|
||||
files: [{ index: 0, path: relative, bytes: wav.length, mimeType: 'audio/wav' }] },
|
||||
};
|
||||
await emitCodingEvent(page, 'snapshot', {
|
||||
type: 'snapshot', conversationId: 'conversation-pi-first-chat', workerGeneration: 1, seq: 10,
|
||||
snapshot: { ...snapshot, cursor: { workerGeneration: 1, seq: 10 }, nodes: [{
|
||||
kind: 'tool', id: 'audio-preview', toolCallId: 'audio-preview', toolName: 'game_sound_generate',
|
||||
title: '生成游戏音效', status: 'complete', inputText: '', output: [], details,
|
||||
}] },
|
||||
});
|
||||
await expect(page.getByTestId('tool-progress-preview')).toContainText('游戏音频 · 已保存');
|
||||
await page.getByTestId('coding-process-group').locator('summary').first().click();
|
||||
await page.locator('[data-node-id="audio-preview"] > summary').click();
|
||||
await expect(page.locator('audio')).toHaveCount(0);
|
||||
await page.getByRole('button', { name: '试听 sound-1.wav' }).click();
|
||||
const player = page.getByLabel('试听 sound-1.wav', { exact: true });
|
||||
await expect(player).toHaveAttribute('controls', '');
|
||||
expect(await player.getAttribute('autoplay')).toBeNull();
|
||||
await expect.poll(() => player.evaluate((element) => (element as HTMLAudioElement).duration)).toBe(0.25);
|
||||
expect(await player.evaluate((element) => (element as HTMLAudioElement).paused)).toBe(true);
|
||||
await page.screenshot({ path: testInfo.outputPath('audio-preview.png') });
|
||||
});
|
||||
|
||||
async function readState(electronApp: ElectronApplication): Promise<{
|
||||
captured: CapturedRequest[];
|
||||
snapshotPending: boolean;
|
||||
|
||||
Reference in New Issue
Block a user