Merge #5005 (futureworld678 Shift+Enter) into stage
This commit is contained in:
commit
78ca4d43a1
5 files changed
+71
-4
No files matched your search
+2
-2
@@ -7792,7 +7792,7 @@ def _get_session_agent_lock(session_id: str) -> threading.Lock:
|
||||
_SETTINGS_DEFAULTS = {
|
||||
"default_workspace": str(DEFAULT_WORKSPACE),
|
||||
"onboarding_completed": False,
|
||||
"send_key": "enter", # 'enter' or 'ctrl+enter'
|
||||
"send_key": "enter", # 'enter', 'ctrl+enter', or 'shift+enter'
|
||||
"show_token_usage": False, # show input/output token badge below assistant messages
|
||||
"show_quota_chip": False, # show ambient provider quota chip in composer footer (default off; wide desktop only when enabled, see style.css @media)
|
||||
"show_conversation_outline": False, # show opt-in desktop jump-to-question outline panel
|
||||
@@ -8022,7 +8022,7 @@ _SETTINGS_ALLOWED_KEYS = set(_SETTINGS_DEFAULTS.keys()) - {
|
||||
"simplified_tool_calling",
|
||||
}
|
||||
_SETTINGS_ENUM_VALUES = {
|
||||
"send_key": {"enter", "ctrl+enter"},
|
||||
"send_key": {"enter", "ctrl+enter", "shift+enter"},
|
||||
"sidebar_density": {"compact", "detailed"},
|
||||
"font_size": {"small", "default", "large", "xlarge"},
|
||||
"auto_title_refresh_every": {"0", "5", "10", "20"},
|
||||
|
||||
+8
-2
@@ -1737,6 +1737,9 @@ $('msg').addEventListener('keydown',e=>{
|
||||
if(e.key==='Escape'){e.preventDefault();e.stopPropagation();hideCmdDropdown();return;}
|
||||
if(e.key==='Enter'&&!e.shiftKey){
|
||||
if(_isImeEnter(e)){return;}
|
||||
if(window._sendKey==='shift+enter'){
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
selectCmdDropdownItem();
|
||||
return;
|
||||
@@ -1750,7 +1753,8 @@ $('msg').addEventListener('keydown',e=>{
|
||||
// doesn't consistently reduce vv.height by >120px. The pointer media query
|
||||
// pair is a sufficient and more reliable signal for "software keyboard only".
|
||||
// Hardware keyboards on tablets are covered by _hasFinePointerCoexisting.
|
||||
// The 'ctrl+enter' setting also uses this behavior (Enter = newline).
|
||||
// The 'ctrl+enter' and 'shift+enter' settings also use this behavior
|
||||
// (plain Enter = newline).
|
||||
// Users can override in Settings by explicitly choosing 'enter' mode.
|
||||
if(e.key==='Enter'){
|
||||
if(_isImeEnter(e)){return;}
|
||||
@@ -1758,7 +1762,9 @@ $('msg').addEventListener('keydown',e=>{
|
||||
const _mobileDefault=matchMedia('(pointer:coarse)').matches
|
||||
&&!_hasFinePointerCoexisting()
|
||||
&&window._sendKey==='enter';
|
||||
if(window._sendKey==='ctrl+enter'||_mobileDefault){
|
||||
if(window._sendKey==='shift+enter'){
|
||||
if(e.shiftKey){e.preventDefault();send();}
|
||||
} else if(window._sendKey==='ctrl+enter'||_mobileDefault){
|
||||
if(isNumpadEnter||e.ctrlKey||e.metaKey){e.preventDefault();send();}
|
||||
} else {
|
||||
if(!e.shiftKey){e.preventDefault();send();}
|
||||
|
||||
@@ -1153,6 +1153,7 @@
|
||||
<select id="settingsSendKey" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px">
|
||||
<option value="enter">Enter (Shift+Enter for newline)</option>
|
||||
<option value="ctrl+enter">Ctrl+Enter (Enter for newline)</option>
|
||||
<option value="shift+enter">Shift+Enter (Enter for newline)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="settings-field">
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"""Keyboard contract for the Shift+Enter send-key preference."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parents[1]
|
||||
BOOT_JS = (REPO / "static" / "boot.js").read_text(encoding="utf-8")
|
||||
INDEX_HTML = (REPO / "static" / "index.html").read_text(encoding="utf-8")
|
||||
CONFIG_PY = (REPO / "api" / "config.py").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _listener_body() -> str:
|
||||
signature = "$('msg').addEventListener('keydown',e=>"
|
||||
start = BOOT_JS.index(signature)
|
||||
brace = BOOT_JS.index("{", start)
|
||||
depth = 0
|
||||
for idx in range(brace, len(BOOT_JS)):
|
||||
char = BOOT_JS[idx]
|
||||
if char == "{":
|
||||
depth += 1
|
||||
elif char == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return BOOT_JS[start : idx + 1]
|
||||
raise AssertionError("could not extract composer keydown listener")
|
||||
|
||||
|
||||
def test_settings_expose_shift_enter_send_key_option():
|
||||
assert '<option value="shift+enter">Shift+Enter (Enter for newline)</option>' in INDEX_HTML
|
||||
assert '"send_key": {"enter", "ctrl+enter", "shift+enter"}' in CONFIG_PY
|
||||
|
||||
|
||||
def test_shift_enter_mode_sends_only_with_shift_enter():
|
||||
handler = _listener_body()
|
||||
shift_branch = handler.split("if(window._sendKey==='shift+enter')", 1)[1]
|
||||
shift_branch = shift_branch.split("} else if(window._sendKey==='ctrl+enter'||_mobileDefault)", 1)[0]
|
||||
assert "if(e.shiftKey){e.preventDefault();send();}" in shift_branch
|
||||
assert "if(!e.shiftKey){e.preventDefault();send();}" not in shift_branch
|
||||
|
||||
|
||||
def test_shift_enter_mode_leaves_plain_enter_to_textarea_when_autocomplete_open():
|
||||
handler = _listener_body()
|
||||
dropdown_enter = handler[
|
||||
handler.index("if(e.key==='Enter'&&!e.shiftKey)") :
|
||||
handler.index("// Send key: respect user preference.")
|
||||
]
|
||||
assert "if(window._sendKey==='shift+enter')" in dropdown_enter
|
||||
guarded = dropdown_enter.split("if(window._sendKey==='shift+enter')", 1)[1].split("e.preventDefault();", 1)[0]
|
||||
assert "return;" in guarded
|
||||
|
||||
|
||||
def test_existing_enter_and_ctrl_enter_modes_stay_available():
|
||||
handler = _listener_body()
|
||||
assert "if(!e.shiftKey){e.preventDefault();send();}" in handler
|
||||
assert "if(isNumpadEnter||e.ctrlKey||e.metaKey){e.preventDefault();send();}" in handler
|
||||
@@ -47,6 +47,12 @@ def test_settings_save_send_key():
|
||||
# Verify it persisted
|
||||
data, _ = get("/api/settings")
|
||||
assert data["send_key"] == "ctrl+enter"
|
||||
# Save shift+enter
|
||||
_, status = post("/api/settings", {"send_key": "shift+enter"})
|
||||
assert status == 200
|
||||
# Verify it persisted
|
||||
data, _ = get("/api/settings")
|
||||
assert data["send_key"] == "shift+enter"
|
||||
finally:
|
||||
# Always restore default
|
||||
post("/api/settings", {"send_key": "enter"})
|
||||
|
||||
Reference in new issue
Block a user