mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
Release God's Eye View as open source
This commit is contained in:
1 parent
a5aaea7e16
commit
880a672b5e
433 files changed
+219893
-23
No files matched your search
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Credentialed AI voice acceptance using a prerecorded Chromium microphone.
|
||||
*
|
||||
* Run: node scripts/qa-voice-wav.mjs http://localhost:4189
|
||||
*/
|
||||
import { createHash } from 'node:crypto';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import puppeteer from 'puppeteer';
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const appUrl = process.argv[2] || 'http://localhost:4189';
|
||||
const wavPath = process.argv[3]
|
||||
|| path.join(repoRoot, 'scripts', 'fixtures', 'voice', 'full-globe-turn-on-radio.wav');
|
||||
const expectedFixtureSha256 = 'b57af70db1922b72fec2c6c58348ccd3309e10aa1e8edec2890277dff26cc7bb';
|
||||
|
||||
if (!wavPath || !fs.existsSync(wavPath)) {
|
||||
console.error(`WAV fixture not found: ${wavPath || '(missing argument)'}`);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const fixtureSha256 = createHash('sha256').update(fs.readFileSync(wavPath)).digest('hex');
|
||||
if (fixtureSha256 !== expectedFixtureSha256) {
|
||||
console.error(`Unexpected WAV fixture SHA-256: ${fixtureSha256}`);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const appOrigin = new URL(appUrl).origin;
|
||||
const browser = await puppeteer.launch({
|
||||
headless: 'new',
|
||||
executablePath: puppeteer.executablePath(),
|
||||
args: [
|
||||
'--no-sandbox',
|
||||
'--disable-setuid-sandbox',
|
||||
'--disable-dev-shm-usage',
|
||||
'--disable-background-timer-throttling',
|
||||
'--disable-renderer-backgrounding',
|
||||
'--use-fake-device-for-media-stream',
|
||||
`--use-file-for-fake-audio-capture=${wavPath}%noloop`,
|
||||
'--autoplay-policy=no-user-gesture-required',
|
||||
'--window-size=1440,900',
|
||||
],
|
||||
});
|
||||
|
||||
try {
|
||||
const context = browser.defaultBrowserContext();
|
||||
await context.overridePermissions(appOrigin, ['microphone']);
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
|
||||
|
||||
const consoleErrors = [];
|
||||
page.on('console', (message) => {
|
||||
if (message.type() === 'error') consoleErrors.push(message.text());
|
||||
});
|
||||
page.on('pageerror', (error) => consoleErrors.push(error.message));
|
||||
|
||||
await page.goto(appUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 });
|
||||
await page.waitForFunction(() => (
|
||||
window.__godsEyeView?.voiceCommands
|
||||
&& document.getElementById('gev-voice-button')
|
||||
), { timeout: 30_000 });
|
||||
|
||||
const readState = () => page.evaluate(() => {
|
||||
const voice = window.__godsEyeView?.voiceCommands;
|
||||
const radio = window.__godsEyeView?.dataManager?.layers?.get('radio')?.module;
|
||||
const radioState = radio?.getUIState?.() || null;
|
||||
const camera = window.__godsEyeView?.viewer?.camera;
|
||||
const cartographic = camera?.positionCartographic;
|
||||
return {
|
||||
at: Date.now(),
|
||||
voiceStatus: voice?.status || null,
|
||||
voiceDetail: document.getElementById('gev-voice-detail')?.textContent?.trim() || null,
|
||||
cameraHeightM: cartographic?.height ?? null,
|
||||
radioEnabled: radioState?.enabled ?? null,
|
||||
radioAudioState: radioState?.audioState ?? null,
|
||||
radioStation: radioState?.selected?.name || null,
|
||||
radioVoiceDucked: radioState?.voiceDucked ?? null,
|
||||
};
|
||||
});
|
||||
|
||||
const initial = await readState();
|
||||
await page.evaluate(() => document.getElementById('gev-voice-button').click());
|
||||
|
||||
const timeline = [initial];
|
||||
let lastSignature = JSON.stringify(initial);
|
||||
const deadline = Date.now() + 75_000;
|
||||
let finalState = initial;
|
||||
while (Date.now() < deadline) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
finalState = await readState();
|
||||
const signature = JSON.stringify({
|
||||
voiceStatus: finalState.voiceStatus,
|
||||
voiceDetail: finalState.voiceDetail,
|
||||
cameraHeightM: Math.round(finalState.cameraHeightM || 0),
|
||||
radioEnabled: finalState.radioEnabled,
|
||||
radioAudioState: finalState.radioAudioState,
|
||||
radioStation: finalState.radioStation,
|
||||
radioVoiceDucked: finalState.radioVoiceDucked,
|
||||
});
|
||||
if (signature !== lastSignature) {
|
||||
timeline.push(finalState);
|
||||
lastSignature = signature;
|
||||
}
|
||||
if (
|
||||
finalState.voiceStatus === 'idle'
|
||||
&& finalState.radioAudioState === 'playing'
|
||||
&& finalState.radioVoiceDucked === false
|
||||
&& Number(finalState.cameraHeightM) >= 10_000_000
|
||||
) break;
|
||||
if (finalState.voiceStatus === 'error') break;
|
||||
}
|
||||
|
||||
const result = {
|
||||
ok: finalState.voiceStatus === 'idle'
|
||||
&& finalState.radioAudioState === 'playing'
|
||||
&& finalState.radioVoiceDucked === false
|
||||
&& Number(finalState.cameraHeightM) >= 10_000_000,
|
||||
fixture: wavPath,
|
||||
fixtureSha256,
|
||||
appUrl,
|
||||
elapsedMs: finalState.at - initial.at,
|
||||
finalState,
|
||||
timeline,
|
||||
consoleErrors,
|
||||
};
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
process.exitCode = result.ok ? 0 : 1;
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
Reference in new issue
Block a user