feat(transcription): add shared voice input support (#4232)

* feat(webui): add voice transcription input

* feat(webui): render ANSI output in code blocks

* refactor(webui): isolate voice recorder logic

* refactor(transcription): keep websocket ingress thin

* refactor(transcription): resolve channel audio settings on demand

* style(webui): neutralize voice waveform color

* feat(webui): add voice input tooltip

* feat(webui): add voice input keyboard shortcut

* fix(webui): distinguish voice shortcut platforms

* fix(webui): place voice button after model selector

* refactor(webui): share voice hold recording helpers

* fix(desktop): allow microphone voice input

* fix(webui): stabilize token usage month labels

* feat(webui): show voice input on settings overview

* fix(webui): label voice capability as recognition

* fix(webui): align capability overview status

* refactor(webui): isolate transcription socket handling

* fix(webui): soften silent voice waveform

* refactor(audio): clarify transcription service location

* docs(transcription): clarify audio and provider boundaries

* fix(exec): reduce session output polling flake
This commit is contained in:
Xubin Ren
2026-06-09 01:08:49 +08:00
committed by GitHub
parent 06d454a225
commit 9c81280300
49 changed files with 3071 additions and 257 deletions
+16 -6
View File
@@ -6,8 +6,12 @@ import shlex
import subprocess
import sys
from nanobot.agent.tools.exec_session import (
ExecSessionManager,
ListExecSessionsTool,
WriteStdinTool,
)
from nanobot.agent.tools.shell import ExecTool
from nanobot.agent.tools.exec_session import ExecSessionManager, ListExecSessionsTool, WriteStdinTool
def _python_command(code: str) -> str:
@@ -141,7 +145,7 @@ def test_exec_can_continue_with_stdin(tmp_path):
return initial, result
initial, result = asyncio.run(run())
assert "ready" in initial
assert "ready" in initial + result
assert "Process running" in initial
assert "Elapsed:" in initial
assert "got:ping" in result
@@ -170,7 +174,7 @@ def test_write_stdin_can_close_stdin(tmp_path):
return initial, result
initial, result = asyncio.run(run())
assert "ready" in initial
assert "ready" in initial + result
assert "got:payload" in result
assert "Stdin closed." in result
assert "Exit code: 0" in result
@@ -185,14 +189,20 @@ def test_write_stdin_can_terminate_session(tmp_path):
"import time; print('ready', flush=True); time.sleep(30)"
)
initial = await exec_tool.execute(command=command, yield_time_ms=500)
initial = await exec_tool.execute(command=command, yield_time_ms=100)
sid = _session_id(initial)
waited = await stdin_tool.execute(
session_id=sid,
wait_for="ready",
wait_timeout_ms=3000,
yield_time_ms=0,
)
result = await stdin_tool.execute(
session_id=sid,
terminate=True,
yield_time_ms=0,
)
return initial, result
return initial + waited, result
initial, result = asyncio.run(run())
assert "ready" in initial
@@ -243,7 +253,7 @@ def test_write_stdin_preserves_completed_session_output_until_polled(tmp_path):
initial, final = asyncio.run(run())
assert "ready" in initial
assert "ready" in initial + final
assert "done" in final
assert "Exit code: 0" in final