mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
feat(voice): voice core — honest layer reach, spoken summary with on-screen card, narration, point-and-ask
- Voice layer manifest (src/voice/layerManifest.js) generates the layer enums, aliases and analyst field hints; a test fails when a catalog layer has no entry. - analyst_query validates fields, operators, units, values and scopes, counts up to 250,000 loaded records per layer, reports complete/partial/unanswered, tells LAYER_OFF / NOT_READY / FEED_UNAVAILABLE apart from zero, refuses mismatched follow-ups and never lets a superseded query replace follow-up memory. Seven more layers expose analyst records. - Tool results carry a code-built say line and a display card; the voice card in the mic control shows captions, plan steps and the result (src/voice/resultDisplay.js is the one adapter). Slow tools narrate their current step out of band; holding Space while the assistant talks cuts it off. Turn spans go to the debug log; caption transcription is metered; the log omits what was said unless GEV_VOICE_LOG_CONTENT=1. - Point and ask: the Space-down pointer snapshot resolves "this", "that one" and "around here" through 'pointer' targets, and "the second one" through referent:n from the last numbered list. - Prompt states each policy once (instructions + tools 48.5k -> 46.7k chars). - Voice benchmark harness (scripts/qa-voice-bench.mjs, scripts/voice-bench/) shares its phrase table and grading with scripts/qa-voice-routing.mjs.
This commit is contained in:
1 parent
e7707d9a0f
commit
a19897ad15
97 files changed
+14343
-755
No files matched your search
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* A subtle reticle at the point "this" resolved to, shown while the turn is
|
||||
* live so the user sees what was understood before the answer.
|
||||
*
|
||||
* It is a fixed-position overlay (pointer-events: none) placed from the
|
||||
* snapshot's canvas pixels, so it never touches the Cesium scene. The reticle
|
||||
* marks where the user pointed, not a tracked world position: it leaves as
|
||||
* soon as a tool moves the camera, when the reply completes, when the session
|
||||
* ends, and after {@link RETICLE_MAX_MS} at most. Clean-UI and recording mode
|
||||
* hide it through CSS.
|
||||
*/
|
||||
|
||||
export const RETICLE_MAX_MS = 10000;
|
||||
export const RETICLE_LINGER_MS = 1500;
|
||||
|
||||
/** Tools that move the camera away from the pointed spot. */
|
||||
export const CAMERA_TOOLS = new Set([
|
||||
'fly_to_location',
|
||||
'track_entity',
|
||||
'zoom_to_globe',
|
||||
'adjust_camera_zoom',
|
||||
'move_camera',
|
||||
'fly_route',
|
||||
'frame_overhead',
|
||||
'select_nearest_aircraft',
|
||||
'control_cockpit',
|
||||
]);
|
||||
|
||||
export class PointerReticle {
|
||||
/**
|
||||
* @param {object} options
|
||||
* @param {Document} options.doc Document that owns the overlay.
|
||||
* @param {() => {left: number, top: number}|null} options.rectOf Canvas
|
||||
* client rect, read at show time.
|
||||
* @param {Function} [options.setTimer]
|
||||
* @param {Function} [options.clearTimer]
|
||||
*/
|
||||
constructor({
|
||||
doc,
|
||||
rectOf,
|
||||
setTimer = (fn, ms) => setTimeout(fn, ms),
|
||||
clearTimer = (id) => clearTimeout(id),
|
||||
}) {
|
||||
Object.assign(this, { doc, rectOf, setTimer, clearTimer });
|
||||
this.node = null;
|
||||
this.timer = null;
|
||||
// Tools running in this turn, keyed by call id (name when absent).
|
||||
this.running = new Set();
|
||||
this.visible = false;
|
||||
this.destroyed = false;
|
||||
}
|
||||
|
||||
ensureNode() {
|
||||
if (this.node || !this.doc?.createElement) return this.node;
|
||||
const node = this.doc.createElement('div');
|
||||
node.className = 'gev-pointer-reticle';
|
||||
node.setAttribute?.('aria-hidden', 'true');
|
||||
node.hidden = true;
|
||||
this.doc.body?.appendChild?.(node);
|
||||
this.node = node;
|
||||
return node;
|
||||
}
|
||||
|
||||
schedule(ms) {
|
||||
this.cancelTimer();
|
||||
this.timer = this.setTimer(() => {
|
||||
this.timer = null;
|
||||
this.hide();
|
||||
}, ms);
|
||||
}
|
||||
|
||||
cancelTimer() {
|
||||
if (this.timer === null) return;
|
||||
this.clearTimer(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
/** Show at a snapshot's canvas pixels. */
|
||||
show(pointer) {
|
||||
if (this.destroyed || !pointer?.screenPx) return;
|
||||
const rect = this.rectOf?.();
|
||||
const node = this.ensureNode();
|
||||
if (!rect || !node) return;
|
||||
node.style.left = `${Math.round(rect.left + pointer.screenPx.x)}px`;
|
||||
node.style.top = `${Math.round(rect.top + pointer.screenPx.y)}px`;
|
||||
node.dataset.target = pointer.kind === 'this' ? 'entity' : 'ground';
|
||||
node.hidden = false;
|
||||
this.visible = true;
|
||||
this.running.clear();
|
||||
this.schedule(RETICLE_MAX_MS);
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.cancelTimer();
|
||||
this.running.clear();
|
||||
if (!this.visible) return;
|
||||
this.visible = false;
|
||||
if (this.node) this.node.hidden = true;
|
||||
}
|
||||
|
||||
/** Follow provider-neutral session events. */
|
||||
handle(event) {
|
||||
if (this.destroyed || !event?.type) return;
|
||||
switch (event.type) {
|
||||
case 'pointer':
|
||||
if (event.pointer) this.show(event.pointer);
|
||||
else this.hide();
|
||||
return;
|
||||
case 'state':
|
||||
if (['idle', 'error'].includes(event.state)) this.hide();
|
||||
return;
|
||||
case 'action-call':
|
||||
if (!this.visible) return;
|
||||
if (CAMERA_TOOLS.has(event.name)) this.hide();
|
||||
else this.running.add(event.callId || event.name);
|
||||
return;
|
||||
case 'action-result':
|
||||
this.running.delete(event.callId || event.name);
|
||||
return;
|
||||
case 'completion':
|
||||
if (this.visible && this.running.size === 0)
|
||||
this.schedule(RETICLE_LINGER_MS);
|
||||
return;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.destroyed) return;
|
||||
this.hide();
|
||||
this.destroyed = true;
|
||||
this.node?.remove?.();
|
||||
this.node = null;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user