mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
feat(voice): reconcile preview and settings with main
add the shared Voice Preview and Settings panel with browser-local preview and inactivity preferences, keyboard controls and responsive positioning preserve main’s Rotation and Context behavior, clarify panel routing and refresh schema pins within the unchanged prompt limit
This commit is contained in:
1 parent
9d3548c27c
commit
5c3b66c263
39 files changed
+5274
-111
No files matched your search
@@ -74,8 +74,17 @@ const PANEL_ALIASES = new Map([
|
||||
['styles', 'control-panel'],
|
||||
['filters', 'control-panel'],
|
||||
['visual styles', 'control-panel'],
|
||||
['visual preset', 'control-panel'],
|
||||
['visual presets', 'control-panel'],
|
||||
['provider settings', 'provider-settings'],
|
||||
['cctv', 'cctv-panel'],
|
||||
['cameras', 'cctv-panel'],
|
||||
['voice settings', 'voice-settings'],
|
||||
['voice-settings', 'voice-settings'],
|
||||
['voice preview', 'voice-preview'],
|
||||
['voice-preview', 'voice-preview'],
|
||||
['voice control', 'voice-preview'],
|
||||
['voice controls', 'voice-preview'],
|
||||
['radio', 'radio-panel'],
|
||||
['internet radio', 'radio-panel'],
|
||||
['radio stations', 'radio-panel'],
|
||||
@@ -110,6 +119,7 @@ const PANEL_IDS = new Set([
|
||||
'pp-toggles',
|
||||
'weather-panel',
|
||||
'recent-imagery-panel',
|
||||
'provider-settings',
|
||||
]);
|
||||
const CONTEXT_MODE_ALIASES = new Map([
|
||||
['off', 'off'],
|
||||
@@ -243,6 +253,7 @@ export function createGevActionRunner({
|
||||
searchNavigation = searchAndFlyTo,
|
||||
speechBuilders = null,
|
||||
deixis = null,
|
||||
readVoicePanelOperation = null,
|
||||
}) {
|
||||
// Voice enable times and analyst follow-up memory belong to this runner.
|
||||
// Speech builders turn results into say/display envelopes; callers extend
|
||||
@@ -268,6 +279,81 @@ export function createGevActionRunner({
|
||||
});
|
||||
// Nested calls (select_nearest_aircraft composing layer + flight) use the
|
||||
// raw runner; only the outer call gets a voice envelope.
|
||||
const voiceUnavailable = (action) => ({
|
||||
ok: false,
|
||||
action,
|
||||
applied: false,
|
||||
unavailable: true,
|
||||
error: 'Voice panel controls are unavailable in this host.',
|
||||
say: 'Voice panel controls are unavailable in this host.',
|
||||
});
|
||||
async function showVoicePanel(
|
||||
view,
|
||||
open,
|
||||
options,
|
||||
operation = readVoicePanelOperation?.(),
|
||||
) {
|
||||
const current = () =>
|
||||
!options.signal?.aborted &&
|
||||
(!options.isCurrent || options.isCurrent()) &&
|
||||
operation?.isCurrent();
|
||||
if (!operation) return voiceUnavailable('set_panel_open');
|
||||
if (!current())
|
||||
return {
|
||||
ok: false,
|
||||
action: 'set_panel_open',
|
||||
applied: false,
|
||||
cancelled: true,
|
||||
};
|
||||
let exit = null;
|
||||
if (open) {
|
||||
if (typeof styleManager?.getCockpitState !== 'function')
|
||||
return voiceUnavailable('set_panel_open');
|
||||
if (styleManager.getCockpitState()?.active) {
|
||||
if (typeof styleManager.controlCockpit !== 'function')
|
||||
return voiceUnavailable('set_panel_open');
|
||||
exit = await styleManager.controlCockpit('exit');
|
||||
if (styleManager.getCockpitState()?.active)
|
||||
return {
|
||||
ok: false,
|
||||
action: 'set_panel_open',
|
||||
applied: false,
|
||||
exit,
|
||||
error: exit?.error || 'Cockpit did not exit.',
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!current())
|
||||
return {
|
||||
ok: false,
|
||||
action: 'set_panel_open',
|
||||
applied: false,
|
||||
cancelled: true,
|
||||
exit,
|
||||
cockpitExited: Boolean(exit && !styleManager.getCockpitState()?.active),
|
||||
say:
|
||||
exit && !styleManager.getCockpitState()?.active
|
||||
? 'Cockpit exited; the voice panel was left as you last set it.'
|
||||
: 'The voice panel was left as you last set it.',
|
||||
};
|
||||
const result = operation.show(view, open);
|
||||
return {
|
||||
...result,
|
||||
action: 'set_panel_open',
|
||||
panelId: view === 'settings' ? 'voice-settings' : 'voice-preview',
|
||||
exit,
|
||||
state: styleManager?.getCockpitState?.() ?? null,
|
||||
say: !result.ok
|
||||
? 'The voice panel was not changed.'
|
||||
: result.settingsOpen
|
||||
? 'Voice Settings is open.'
|
||||
: result.panelOpen
|
||||
? result.previewEnabled
|
||||
? 'Voice Preview is open.'
|
||||
: 'Voice control is open; Preview remains off.'
|
||||
: 'The voice panel is closed.',
|
||||
};
|
||||
}
|
||||
const runGevAction = async function runGevAction(
|
||||
name,
|
||||
rawArgs = {},
|
||||
@@ -639,6 +725,25 @@ export function createGevActionRunner({
|
||||
return { ok: true, action: 'set_visual_style', style };
|
||||
}
|
||||
|
||||
if (name === 'set_voice_inactivity_timeout') {
|
||||
const operation = readVoicePanelOperation?.();
|
||||
if (!operation) return voiceUnavailable(name);
|
||||
if (!current() || !operation.isCurrent())
|
||||
return { ok: false, action: name, applied: false, cancelled: true };
|
||||
const result = operation.setMinutes(args.minutes);
|
||||
const duration =
|
||||
result.minutes === 0 ? 'disabled' : `${result.minutes} minutes`;
|
||||
return {
|
||||
...result,
|
||||
action: name,
|
||||
say: !result.applied
|
||||
? 'Voice inactivity was not changed.'
|
||||
: result.persisted
|
||||
? `Voice inactivity is ${duration}, saved in this browser.`
|
||||
: `Voice inactivity is ${duration} for now, but could not be saved in this browser.`,
|
||||
};
|
||||
}
|
||||
|
||||
if (name === 'set_panel_open') {
|
||||
const panelId = normalizePanelId(args.panelId || args.panel);
|
||||
if (!panelId)
|
||||
@@ -646,6 +751,70 @@ export function createGevActionRunner({
|
||||
`Unknown panel: ${args.panelId || args.panel || 'missing'}`,
|
||||
);
|
||||
const open = args.open !== false;
|
||||
if (panelId === 'voice-settings' || panelId === 'voice-preview')
|
||||
return showVoicePanel(
|
||||
panelId === 'voice-settings' ? 'settings' : 'preview',
|
||||
open,
|
||||
runOptions,
|
||||
);
|
||||
if (panelId === 'provider-settings') {
|
||||
if (!current())
|
||||
return {
|
||||
ok: false,
|
||||
action: name,
|
||||
panelId,
|
||||
applied: false,
|
||||
cancelled: true,
|
||||
};
|
||||
const doc = globalThis.document;
|
||||
const panel = doc?.getElementById?.('key-setup');
|
||||
const button = open
|
||||
? doc?.getElementById?.('key-setup-chip')
|
||||
: panel?.querySelector?.('[data-key-setup-close]');
|
||||
const concealed =
|
||||
open &&
|
||||
[
|
||||
'ui-clean-view',
|
||||
'recording-mode',
|
||||
'cockpit-mode',
|
||||
'scene-playback-mode',
|
||||
].some((mode) => doc?.body?.classList?.contains(mode));
|
||||
if (
|
||||
concealed ||
|
||||
panel?.dataset?.ready !== 'true' ||
|
||||
typeof button?.click !== 'function'
|
||||
) {
|
||||
const error = concealed
|
||||
? 'Provider Settings is hidden in the current view. Leave that view before opening it.'
|
||||
: 'Provider Settings is unavailable in this runtime.';
|
||||
return {
|
||||
ok: false,
|
||||
action: name,
|
||||
panelId,
|
||||
applied: false,
|
||||
unavailable: true,
|
||||
error,
|
||||
say: error,
|
||||
};
|
||||
}
|
||||
button.click();
|
||||
// Opening unhides immediately; closing removes visible before its fade ends.
|
||||
const applied = open
|
||||
? !panel.hidden
|
||||
: !panel.classList.contains('visible');
|
||||
const say = applied
|
||||
? `Provider Settings is ${open ? 'open' : 'closed'}.`
|
||||
: 'Provider Settings did not change.';
|
||||
return {
|
||||
ok: applied,
|
||||
action: name,
|
||||
panelId,
|
||||
applied,
|
||||
open: applied ? open : !panel.hidden,
|
||||
say,
|
||||
...(!applied ? { error: say } : {}),
|
||||
};
|
||||
}
|
||||
setPanelOpen(styleManager, panelId, open);
|
||||
return { ok: true, action: 'set_panel_open', panelId, open };
|
||||
}
|
||||
@@ -734,6 +903,25 @@ export function createGevActionRunner({
|
||||
error: `Unknown cockpit action: ${args.action || args.command || 'missing'}`,
|
||||
};
|
||||
}
|
||||
if (args.voicePanel !== undefined) {
|
||||
if (
|
||||
action !== 'exit' ||
|
||||
!['preview', 'settings'].includes(args.voicePanel)
|
||||
)
|
||||
return {
|
||||
ok: false,
|
||||
action: 'control_cockpit',
|
||||
applied: false,
|
||||
error: 'Voice panel reveal requires exit and a valid view.',
|
||||
};
|
||||
const result = await showVoicePanel(args.voicePanel, true, runOptions);
|
||||
return {
|
||||
...result,
|
||||
action: 'control_cockpit',
|
||||
cockpitAction: 'exit',
|
||||
voicePanel: args.voicePanel,
|
||||
};
|
||||
}
|
||||
const inferred = normalizeCockpitNavigationHints(rawAction);
|
||||
const targetLayer = normalizeCockpitTargetLayer(
|
||||
args.targetLayer || inferred.targetLayer || args.layer || args.layerId,
|
||||
|
||||
Reference in new issue
Block a user