mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
feat(app): voice on the Navara engine
createViewerlessTools creates the voice commands with viewer: null on the engine (tools.js passes it the voice options and place search), so window.__gevVoiceCommands exists in ?engine=navara: the W1 tools drive the engine and the viewer-only tools answer UNAVAILABLE_ON_ENGINE. The Cesium path hands its engine to initGevVoiceCommands explicitly. viewerlessTools.test.mjs: the Navara tools now return voice commands (created without DOM through injected control and session stand-ins), whose runner answers get_current_view_state on the engine and refuses control_scene; the debug handle carries them and teardown removes them.
This commit is contained in:
1 parent
bc281ab246
commit
18b5475b8a
3 files changed
+74
-12
No files matched your search
+28
-3
@@ -12,6 +12,7 @@ import {
|
|||||||
installRenderGovernor,
|
installRenderGovernor,
|
||||||
uninstallRenderGovernor,
|
uninstallRenderGovernor,
|
||||||
} from '../renderGovernor.js';
|
} from '../renderGovernor.js';
|
||||||
|
import { initGevVoiceCommands } from '../voice/gevRealtime.js';
|
||||||
|
|
||||||
/** The Navara globe module, loaded on demand. */
|
/** The Navara globe module, loaded on demand. */
|
||||||
const loadNavaraGlobeModule = async () =>
|
const loadNavaraGlobeModule = async () =>
|
||||||
@@ -109,14 +110,17 @@ export async function createNavaraScene({
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The tools without a viewer (the experimental Navara engine): the startup
|
* The tools without a viewer (the experimental Navara engine): the startup
|
||||||
* chrome, the render governor on the engine, hidden-tab handling and the
|
* chrome, the render governor on the engine, hidden-tab handling, voice on
|
||||||
* debug handle. The shell shows which features are unavailable.
|
* the engine and the debug handle. The shell shows which features are
|
||||||
|
* unavailable.
|
||||||
*/
|
*/
|
||||||
export function createViewerlessTools({
|
export function createViewerlessTools({
|
||||||
scene: { engine, mapStackController, operations },
|
scene: { engine, mapStackController, operations },
|
||||||
controls: { styleManager },
|
controls: { styleManager },
|
||||||
data,
|
data,
|
||||||
loadingScreen,
|
loadingScreen,
|
||||||
|
placeSearch,
|
||||||
|
voice = {},
|
||||||
startChrome,
|
startChrome,
|
||||||
signal,
|
signal,
|
||||||
defer,
|
defer,
|
||||||
@@ -170,5 +174,26 @@ export function createViewerlessTools({
|
|||||||
defer(() => {
|
defer(() => {
|
||||||
if (window.__godsEyeView === debug) delete window.__godsEyeView;
|
if (window.__godsEyeView === debug) delete window.__godsEyeView;
|
||||||
});
|
});
|
||||||
return { sceneDirector: null, annotations: null, voiceCommands: null };
|
// Voice on the engine (phase 3, Task 9): the W1 tools drive the engine;
|
||||||
|
// the viewer-only ones (director, annotations, cockpit, radio…) refuse.
|
||||||
|
const voiceCommands = initGevVoiceCommands({
|
||||||
|
...voice,
|
||||||
|
annotationResolver: operations.annotationResolver,
|
||||||
|
searchNavigation: operations.searchAndFlyTo,
|
||||||
|
signal,
|
||||||
|
placeSearch,
|
||||||
|
viewer: null,
|
||||||
|
engine,
|
||||||
|
styleManager,
|
||||||
|
dataManager,
|
||||||
|
sceneDirector: null,
|
||||||
|
annotations: null,
|
||||||
|
});
|
||||||
|
defer(() => {
|
||||||
|
voiceCommands.stop({ removeUi: true });
|
||||||
|
if (window.__gevVoiceCommands === voiceCommands)
|
||||||
|
delete window.__gevVoiceCommands;
|
||||||
|
});
|
||||||
|
debug.voiceCommands = voiceCommands;
|
||||||
|
return { sceneDirector: null, annotations: null, voiceCommands };
|
||||||
}
|
}
|
||||||
+6
-2
@@ -17,8 +17,9 @@ import {
|
|||||||
/**
|
/**
|
||||||
* Attach scene tools, rendering listeners and the application debug handle.
|
* Attach scene tools, rendering listeners and the application debug handle.
|
||||||
* Without a viewer (the experimental Navara engine) the viewer-bound tools
|
* Without a viewer (the experimental Navara engine) the viewer-bound tools
|
||||||
* (director, annotations, drawing, imagery box, scope mask, tracking, voice,
|
* (director, annotations, drawing, imagery box, scope mask, tracking,
|
||||||
* embedded views) are not created; their handles are null.
|
* embedded views) are not created; their handles are null. Voice runs on
|
||||||
|
* the engine there (src/app/navaraMode.js).
|
||||||
*/
|
*/
|
||||||
export function createApplicationTools({
|
export function createApplicationTools({
|
||||||
scene,
|
scene,
|
||||||
@@ -44,6 +45,8 @@ export function createApplicationTools({
|
|||||||
controls,
|
controls,
|
||||||
data,
|
data,
|
||||||
loadingScreen,
|
loadingScreen,
|
||||||
|
placeSearch,
|
||||||
|
voice,
|
||||||
startChrome,
|
startChrome,
|
||||||
signal,
|
signal,
|
||||||
defer,
|
defer,
|
||||||
@@ -189,6 +192,7 @@ export function createApplicationTools({
|
|||||||
signal,
|
signal,
|
||||||
placeSearch,
|
placeSearch,
|
||||||
viewer,
|
viewer,
|
||||||
|
engine,
|
||||||
styleManager,
|
styleManager,
|
||||||
dataManager,
|
dataManager,
|
||||||
sceneDirector,
|
sceneDirector,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
|
|
||||||
// The tools without a viewer (the experimental Navara engine); they load
|
// The tools without a viewer (the experimental Navara engine); they load
|
||||||
// from src/app/navaraMode.js.
|
// from src/app/navaraMode.js.
|
||||||
test('without a viewer the tools run the governor on the engine, show the notice and create no viewer tools', async (t) => {
|
test('without a viewer the tools run the governor on the engine, show the notice, create no viewer tools and run voice on the engine', async (t) => {
|
||||||
const saved = { window: globalThis.window, document: globalThis.document };
|
const saved = { window: globalThis.window, document: globalThis.document };
|
||||||
const listeners = new Map();
|
const listeners = new Map();
|
||||||
globalThis.window = {};
|
globalThis.window = {};
|
||||||
@@ -34,7 +34,9 @@ test('without a viewer the tools run the governor on the engine, show the notice
|
|||||||
else globalThis[name] = value;
|
else globalThis[name] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const engine = createFakeEngine({ kind: 'navara' });
|
// The voice pointer listens on the engine's canvas.
|
||||||
|
const canvas = new EventTarget();
|
||||||
|
const engine = createFakeEngine({ kind: 'navara', canvas });
|
||||||
const notices = [];
|
const notices = [];
|
||||||
const chrome = [];
|
const chrome = [];
|
||||||
const cleanups = [];
|
const cleanups = [];
|
||||||
@@ -43,7 +45,27 @@ test('without a viewer the tools run the governor on the engine, show the notice
|
|||||||
_showGlobalStatusNotice: (label, options) =>
|
_showGlobalStatusNotice: (label, options) =>
|
||||||
notices.push({ label, options }),
|
notices.push({ label, options }),
|
||||||
};
|
};
|
||||||
const dataManager = { layers: new Map() };
|
const dataManager = { layers: new Map(), getAll: () => [] };
|
||||||
|
// Voice without DOM or provider: a control and session stand-in.
|
||||||
|
const button = new EventTarget();
|
||||||
|
button.setAttribute = () => {};
|
||||||
|
let uiRemoved = 0;
|
||||||
|
const voice = {
|
||||||
|
createControl: () => ({
|
||||||
|
button,
|
||||||
|
root: { dataset: {}, remove: () => uiRemoved++ },
|
||||||
|
status: {},
|
||||||
|
detail: {},
|
||||||
|
}),
|
||||||
|
createCard: () => null,
|
||||||
|
createSession: (hooks) => ({
|
||||||
|
runner: (voice.runner = hooks.runner),
|
||||||
|
async start() {},
|
||||||
|
stop() {},
|
||||||
|
sendText() {},
|
||||||
|
sendMapEvent() {},
|
||||||
|
}),
|
||||||
|
};
|
||||||
const tools = await createApplicationTools({
|
const tools = await createApplicationTools({
|
||||||
scene: {
|
scene: {
|
||||||
viewer: null,
|
viewer: null,
|
||||||
@@ -52,6 +74,7 @@ test('without a viewer the tools run the governor on the engine, show the notice
|
|||||||
mapStackController: { kind: 'maps' },
|
mapStackController: { kind: 'maps' },
|
||||||
operations: { surface: { kind: 'surface' } },
|
operations: { surface: { kind: 'surface' } },
|
||||||
},
|
},
|
||||||
|
voice,
|
||||||
controls: { styleManager, weatherEffects: null, cockpitCloudEffects: null },
|
controls: { styleManager, weatherEffects: null, cockpitCloudEffects: null },
|
||||||
data: { dataManager, presentation: { flushVisible: () => flushed++ } },
|
data: { dataManager, presentation: { flushVisible: () => flushed++ } },
|
||||||
loadingScreen: { kind: 'loading' },
|
loadingScreen: { kind: 'loading' },
|
||||||
@@ -62,11 +85,18 @@ test('without a viewer the tools run the governor on the engine, show the notice
|
|||||||
signal: new AbortController().signal,
|
signal: new AbortController().signal,
|
||||||
defer: (dispose) => cleanups.push(dispose),
|
defer: (dispose) => cleanups.push(dispose),
|
||||||
});
|
});
|
||||||
assert.deepEqual(tools, {
|
assert.equal(tools.sceneDirector, null);
|
||||||
sceneDirector: null,
|
assert.equal(tools.annotations, null);
|
||||||
annotations: null,
|
assert.ok(tools.voiceCommands, 'voice runs on the engine');
|
||||||
voiceCommands: null,
|
assert.equal(window.__gevVoiceCommands, tools.voiceCommands);
|
||||||
|
// The W1 tools drive the engine; the viewer-only ones refuse.
|
||||||
|
const view = await voice.runner('get_current_view_state');
|
||||||
|
assert.equal(view.ok, true);
|
||||||
|
const scene = await voice.runner('control_scene', {
|
||||||
|
action: 'list',
|
||||||
});
|
});
|
||||||
|
assert.equal(scene.ok, false);
|
||||||
|
assert.equal(scene.code, 'UNAVAILABLE_ON_ENGINE');
|
||||||
assert.equal(chrome.length, 1);
|
assert.equal(chrome.length, 1);
|
||||||
assert.equal(chrome[0].initializeWelcome, null, 'no first-run welcome');
|
assert.equal(chrome[0].initializeWelcome, null, 'no first-run welcome');
|
||||||
assert.equal(chrome[0].styleManager, styleManager);
|
assert.equal(chrome[0].styleManager, styleManager);
|
||||||
@@ -103,9 +133,12 @@ test('without a viewer the tools run the governor on the engine, show the notice
|
|||||||
assert.equal(debug.engine, engine);
|
assert.equal(debug.engine, engine);
|
||||||
assert.equal(debug.sceneDirector, null);
|
assert.equal(debug.sceneDirector, null);
|
||||||
assert.equal(debug.dataManager, dataManager);
|
assert.equal(debug.dataManager, dataManager);
|
||||||
|
assert.equal(debug.voiceCommands, tools.voiceCommands);
|
||||||
listeners.get('visibilitychange')();
|
listeners.get('visibilitychange')();
|
||||||
assert.equal(flushed, 1);
|
assert.equal(flushed, 1);
|
||||||
while (cleanups.length) await cleanups.pop()();
|
while (cleanups.length) await cleanups.pop()();
|
||||||
assert.equal(window.__godsEyeView, undefined);
|
assert.equal(window.__godsEyeView, undefined);
|
||||||
|
assert.equal(window.__gevVoiceCommands, undefined);
|
||||||
|
assert.equal(uiRemoved, 1, 'the voice control is removed');
|
||||||
assert.equal(listeners.size, 0);
|
assert.equal(listeners.size, 0);
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user