fix(voice): show the voice panel that typed chat asks for

set_panel_open for voice-settings or voice-preview reported success while
chat's open state kept the voice panel hidden, minimized chat included. A
successful explicit show now closes chat, which keeps the typed session,
its running turn and its messages; reopening chat restores them.
This commit is contained in:
Sameh Khamis committed 2026-11-03 15:54:54 -08:00
1 parent b6967a3dd9
commit 4f4af9c4e0
3 files changed
+72 -4

No files matched your search

+54 -2
View File
@@ -511,7 +511,7 @@ test('the voice inactivity timeout never ends a typed chat session', async (t) =
async function directActions(
t,
{ storage = null, delayExit = false, exposePanel = true } = {},
{ storage = null, delayExit = false, exposePanel = true, typed = false } = {},
) {
let f, releaseExit;
const state = { active: true, exits: 0, visual: [] };
@@ -544,7 +544,12 @@ async function directActions(
exposePanel ? f?.commands.captureVoicePanelOperation() : undefined,
});
f = mounted(t, { native: true, runner, storage });
await f.commands.session.start();
// A typed chat session starts from chat's first send instead.
if (typed) {
const adapter = f.instances[0];
adapter.getStartOptions = () => ({ textOnly: true });
adapter.sendText = () => true;
} else await f.commands.session.start();
return {
...f,
runner,
@@ -618,6 +623,53 @@ test('named voice settings exits Cockpit directly; Preview stays disabled and vi
);
});
for (const minimized of [false, true])
test(`an explicit voice panel request from ${minimized ? 'minimized ' : ''}typed chat shows the panel and keeps the chat`, async (t) => {
const f = await directActions(t, { typed: true });
const adapter = f.instances[0];
f.ui.emit('chat-button');
f.ui.emit('chat-input', 'Fly to Tokyo');
f.ui.emit('chat-submit');
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(f.ui.state.chat.busy, true, 'the typed turn is running');
if (minimized) f.ui.emit('chat-minimize');
assert.equal(f.ui.state.root.chatOpen, true);
for (const [panelId, view] of [
['voice-settings', 'settings'],
['voice-preview', 'preview'],
]) {
const result = await f.run('set_panel_open', { panelId, open: true });
assert.equal(result.ok, true);
// `[data-chat-open='true'] .gev-voice-panel` is display:none.
assert.equal(f.ui.state.root.chatOpen, false, panelId);
assert.equal(f.ui.state.chat.open, false);
assert.equal(f.ui.state.voicePanel.hidden, false);
assert.equal(f.ui.state.voicePanel.view, view);
}
assert.equal(adapter.stops, 0, 'the typed session continues');
assert.equal(f.commands.session.isActive(), true);
assert.equal(f.ui.state.chat.busy, true, 'the turn is not cancelled');
adapter.hooks.emit({
type: 'transcript',
role: 'assistant',
text: 'Flying to Tokyo.',
final: true,
itemId: 'reply',
});
adapter.hooks.emit({ type: 'completion', status: 'completed' });
assert.equal(f.ui.state.chat.busy, false);
f.ui.emit('chat-button');
assert.equal(f.ui.state.root.chatOpen, true);
assert.equal(f.ui.state.voicePanel.hidden, true);
assert.deepEqual(
f.ui.chatMessages.map(({ role }) => role),
['user', 'assistant'],
'reopened chat shows the whole conversation',
);
assert.equal(f.ui.chatMessages[0].text, 'Fly to Tokyo');
assert.equal(adapter.stops, 0);
});
test('canonical exit reveals requested voice UI; newer manual choice or Stop defeats pending reveal', async (t) => {
const f = await directActions(t, { delayExit: true });
const pending = f.run('control_cockpit', {
+14 -1
View File
@@ -157,7 +157,20 @@ export function createVoiceCommands({
controls.voiceCard = card;
if (panelMount) {
controls.voicePanel = panelMount.panel;
controls.captureVoicePanelOperation = panelMount.captureOperation;
// An explicit, successful show hands the frame from chat to the voice
// panel, which chat's open state hides. Closing chat keeps its typed
// session, running turn and messages; reopening chat restores them.
controls.captureVoicePanelOperation = () => {
const operation = panelMount.captureOperation();
return {
...operation,
show(view, open = true) {
const result = operation.show(view, open);
if (open && result.ok) chat?.close();
return result;
},
};
};
controls.getVoicePanelState = panelMount.snapshot;
controls.getVoicePreferences = panelHost.snapshot;
}