fix: mic appends to existing textarea text instead of replacing it
Previously, tapping the mic button would reset the textarea each time, clobbering anything the user had already typed or previously dictated. Fix: - Capture _prefix = ta.value when recording starts (btn.onclick) - onresult writes _prefix + (final || interim) so live interim text appears after the existing content, not replacing it - onend commits _prefix + _finalText with smart space insertion: if the prefix doesn't end with a space or newline, a space is added before the new transcript so words don't run together - _prefix is reset to '' in _setRecording(false) so each new recording session starts with a fresh snapshot Behaviour now: tap mic, speak, tap again (or wait for auto-stop) -> transcript is appended to whatever was in the textarea. Tap mic again -> continues appending further. Text stays fully editable before send. tests/test_sprint20.py: 6 new tests covering prefix capture, onresult prepend, onend commit, reset, and smart spacing (52 total, 382 overall).
This commit is contained in:
@@ -27,12 +27,13 @@ $('btnAttach').onclick=()=>$('fileInput').click();
|
|||||||
recognition.lang='en-US';
|
recognition.lang='en-US';
|
||||||
|
|
||||||
let _finalText='';
|
let _finalText='';
|
||||||
|
let _prefix='';
|
||||||
|
|
||||||
function _setRecording(on){
|
function _setRecording(on){
|
||||||
window._micActive=on;
|
window._micActive=on;
|
||||||
btn.classList.toggle('recording',on);
|
btn.classList.toggle('recording',on);
|
||||||
status.style.display=on?'':'none';
|
status.style.display=on?'':'none';
|
||||||
if(!on) _finalText='';
|
if(!on){ _finalText=''; _prefix=''; }
|
||||||
}
|
}
|
||||||
|
|
||||||
recognition.onstart=()=>{ _finalText=''; };
|
recognition.onstart=()=>{ _finalText=''; };
|
||||||
@@ -45,14 +46,20 @@ $('btnAttach').onclick=()=>$('fileInput').click();
|
|||||||
if(event.results[i].isFinal){ final+=t; _finalText=final; }
|
if(event.results[i].isFinal){ final+=t; _finalText=final; }
|
||||||
else{ interim+=t; }
|
else{ interim+=t; }
|
||||||
}
|
}
|
||||||
ta.value=final||interim;
|
// Append to whatever was already in the textarea before mic started
|
||||||
|
ta.value=_prefix+(final||interim);
|
||||||
autoResize();
|
autoResize();
|
||||||
};
|
};
|
||||||
|
|
||||||
recognition.onend=()=>{
|
recognition.onend=()=>{
|
||||||
|
// Commit: prefix + final transcription; trim trailing space if prefix was non-empty
|
||||||
|
const committed=_finalText
|
||||||
|
? (_prefix&&!_prefix.endsWith(' ')&&!_prefix.endsWith('\n')
|
||||||
|
? _prefix+' '+_finalText.trimStart()
|
||||||
|
: _prefix+_finalText)
|
||||||
|
: ta.value; // no speech detected — leave whatever is there
|
||||||
_setRecording(false);
|
_setRecording(false);
|
||||||
// Ensure textarea has the committed final text (handles auto-stop on silence)
|
ta.value=committed;
|
||||||
if(_finalText) ta.value=_finalText;
|
|
||||||
autoResize();
|
autoResize();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -77,6 +84,8 @@ $('btnAttach').onclick=()=>$('fileInput').click();
|
|||||||
// _setRecording(false) will be called by onend
|
// _setRecording(false) will be called by onend
|
||||||
} else {
|
} else {
|
||||||
_finalText='';
|
_finalText='';
|
||||||
|
// Snapshot existing textarea content so we append rather than replace
|
||||||
|
_prefix=ta.value;
|
||||||
recognition.start();
|
recognition.start();
|
||||||
_setRecording(true);
|
_setRecording(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -335,6 +335,66 @@ def test_boot_js_autoresize_called():
|
|||||||
assert 'autoResize()' in js
|
assert 'autoResize()' in js
|
||||||
|
|
||||||
|
|
||||||
|
# ── Append behaviour (fix: mic appends to existing text, not replace) ────
|
||||||
|
|
||||||
|
|
||||||
|
def test_boot_js_prefix_variable_declared():
|
||||||
|
"""boot.js must declare _prefix variable to snapshot pre-existing textarea content."""
|
||||||
|
js, _ = get_text("/static/boot.js")
|
||||||
|
assert "_prefix" in js
|
||||||
|
|
||||||
|
|
||||||
|
def test_boot_js_prefix_captured_on_start():
|
||||||
|
"""_prefix must be set from ta.value when the user starts recording."""
|
||||||
|
js, _ = get_text("/static/boot.js")
|
||||||
|
# _prefix assignment must happen in the btn.onclick else branch (before recognition.start)
|
||||||
|
btn_onclick_idx = js.find("btn.onclick")
|
||||||
|
btn_onclick_end = js.find("};", btn_onclick_idx)
|
||||||
|
onclick_body = js[btn_onclick_idx:btn_onclick_end]
|
||||||
|
assert "_prefix=ta.value" in onclick_body or "_prefix = ta.value" in onclick_body
|
||||||
|
|
||||||
|
|
||||||
|
def test_boot_js_onresult_prepends_prefix():
|
||||||
|
"""onresult must include _prefix when writing to textarea (append, not replace)."""
|
||||||
|
js, _ = get_text("/static/boot.js")
|
||||||
|
onresult_idx = js.find("recognition.onresult")
|
||||||
|
onresult_end = js.find("};", onresult_idx)
|
||||||
|
onresult_body = js[onresult_idx:onresult_end]
|
||||||
|
# ta.value must be set to _prefix + something, not just the transcript alone
|
||||||
|
assert "_prefix" in onresult_body
|
||||||
|
|
||||||
|
|
||||||
|
def test_boot_js_onend_commits_with_prefix():
|
||||||
|
"""onend must commit _prefix + _finalText so appended text survives after recognition ends."""
|
||||||
|
js, _ = get_text("/static/boot.js")
|
||||||
|
onend_idx = js.find("recognition.onend")
|
||||||
|
onend_end = js.find("};", onend_idx)
|
||||||
|
onend_body = js[onend_idx:onend_end]
|
||||||
|
assert "_prefix" in onend_body
|
||||||
|
|
||||||
|
|
||||||
|
def test_boot_js_prefix_reset_on_stop():
|
||||||
|
"""_prefix must be reset when recording stops so next session starts clean."""
|
||||||
|
js, _ = get_text("/static/boot.js")
|
||||||
|
# _setRecording(false) clears both _finalText and _prefix
|
||||||
|
set_rec_idx = js.find("function _setRecording")
|
||||||
|
set_rec_end = js.find("}", set_rec_idx) + 1
|
||||||
|
fn_body = js[set_rec_idx:set_rec_end]
|
||||||
|
assert "_prefix" in fn_body
|
||||||
|
|
||||||
|
|
||||||
|
def test_boot_js_auto_space_between_prefix_and_transcript():
|
||||||
|
"""onend must insert a space between existing text and new transcript when needed."""
|
||||||
|
js, _ = get_text("/static/boot.js")
|
||||||
|
onend_idx = js.find("recognition.onend")
|
||||||
|
onend_end = js.find("};", onend_idx)
|
||||||
|
onend_body = js[onend_idx:onend_end]
|
||||||
|
# Should handle spacing — look for trimStart or endsWith(' ') check
|
||||||
|
has_spacing = ("trimStart" in onend_body or "endsWith(' ')" in onend_body
|
||||||
|
or "endsWith(\" \")" in onend_body or "endsWith('\\n')" in onend_body)
|
||||||
|
assert has_spacing, "onend should handle spacing between prefix and new transcript"
|
||||||
|
|
||||||
|
|
||||||
# ── Regression: existing behaviour unchanged ──────────────────────────────
|
# ── Regression: existing behaviour unchanged ──────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user