Repository navigation
Conversation
|
Important Review skippedReview was skipped as selected files did not have any reviewable changes. ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 Walkthrough
Priority: ⬇️ Low Severity of issue fixed: Low Merge Risk: 🔵 Low · up to Code nested inside a list can still be spoken. Fix that filtering gap before merging, or explicitly accept the limited impact. Pre-merge checks |
|
…dary, and drop indented code from speech (#13103, #17953) #13103: the first audio of a streamed reply waited for a terminated sentence of 20+ chars. createTtsStream (utils/ttsSentences) releases the OPENING chunk at the first clause boundary past OPENING_CLAUSE_MIN_CHARS, or at a word boundary once OPENING_WORD_MIN_CHARS is reached; everything after keeps the terminated-sentence rule. The fast path never fires when a sentence has terminated (a short one is held for merging), never for a reply already under way, and the cursor advances by the exact span. The thresholds are model-specific and are settled by a listening test against our TTS worker before merge. ChatInterface delegates to the stream and its MIN chars constant moves next to the sentence logic (1527 -> 1522). #17953: indented code blocks are dropped from the spoken copy -- a run of 4-space/tab lines after a blank line that is not list content.
23b0d87 to
2007be4
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository: mrveiss/AutoBot-AI/.coderabbit.yaml
- Review profile: ASSERTIVE
- Plan: Advanced
- Run ID:
fe91eecc-1591-4013-bb64-7021e4e3a86e
📒 Files selected for processing (4)
autobot-frontend/src/components/chat/ChatInterface.vueautobot-frontend/src/utils/__tests__/ttsFastStart.test.tsautobot-frontend/src/utils/__tests__/ttsSpeechShaping.test.tsautobot-frontend/src/utils/ttsSentences.ts
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.
| recordLine(line: string, eligible: boolean): boolean { | ||
| const blank = line.trim() === '' | ||
| const indented = INDENTED_RE.test(line) | ||
| if (inCode && !blank && !indented) inCode = false | ||
| if (!inCode && eligible && prevBlank && indented && !blank && !inList) inCode = true | ||
| if (!blank) inList = LIST_ITEM_RE.test(line) || (inList && indented) | ||
| prevBlank = blank | ||
| return !inCode |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
sed -n '110,218p' autobot-frontend/src/utils/ttsSentences.ts
sed -n '101,140p' autobot-frontend/src/utils/__tests__/ttsSpeechShaping.test.tsRepository: mrveiss/AutoBot-AI
Length of output: 6349
🏁 Script executed:
sed -n '1,115p' autobot-frontend/src/utils/ttsSentences.ts
sed -n '140,170p' autobot-frontend/src/utils/ttsSentences.ts
sed -n '90,145p' autobot-frontend/src/utils/__tests__/ttsSpeechShaping.test.ts
git diff --unified=80 1c8f70b586bfcae61d262023730dcacd8399410d 7ae620f3dcdc11f57340f19451d703cf8a59074f -- autobot-frontend/src/utils/ttsSentences.ts autobot-frontend/src/utils/__tests__/ttsSpeechShaping.test.tsRepository: mrveiss/AutoBot-AI
Length of output: 27917
🌐 Web query:
CommonMark specification indented code block inside list item blank line 8 spaces
💡 Result:
In CommonMark, **eight spaces before code text can be correct inside a list item**, but the exact threshold depends on the list marker. For `- `, the list content starts after 2 columns, so an indented code block needs 4 more spaces: **6 spaces total**. Eight spaces also works, with the extra two spaces preserved in the code content. ([spec.commonmark.org](https://spec.commonmark.org/spec?utm_source=openai))
A **truly blank line doesn’t need indentation**. If you mean a line containing eight spaces, it counts as blank; inside a code block, blank lines are preserved. ([spec.commonmark.org](https://spec.commonmark.org/spec?utm_source=openai))
```markdown
- item
code
more code
```
This puts an indented code block inside the list item. A blank line is needed before the code block when it follows paragraph text. ([spec.commonmark.org](https://spec.commonmark.org/spec?utm_source=openai))
Citations:
- 1: https://spec.commonmark.org/spec?utm_source=openai
- 2: https://spec.commonmark.org/spec?utm_source=openai
- 3: https://spec.commonmark.org/spec?utm_source=openai
Filter indented code blocks inside list items.
After - item\n\n, inList remains true. The eight-space secret() line therefore bypasses the !inList check and reaches speech output. CommonMark treats this as an indented code block inside the list item, not list prose. Track the list item's content indentation so the existing four-space nested-list case remains spoken.
Suggested fix
diff --git a/autobot-frontend/src/utils/ttsSentences.ts b/autobot-frontend/src/utils/ttsSentences.ts
@@
const INDENTED_RE = /^(?: {4}| {0,3}\t)/
-const LIST_ITEM_RE = /^[ \t]*(?:[-*+]|\d+[.)])[ \t]/
+const LIST_ITEM_RE = /^[ \t]*(?:[-*+]|\d+[.)])[ \t]+/
+const leadingIndent = (line: string): number => line.match(/^[ \t]*/)?.[0].length ?? 0
function createIndentedCodeTracker() {
let prevBlank = true
let inList = false
+ let listContentIndent: number | null = null
let inCode = false
return {
@@
const blank = line.trim() === ''
const indented = INDENTED_RE.test(line)
+ const indent = leadingIndent(line)
+ const listItem = LIST_ITEM_RE.test(line)
if (inCode && !blank && !indented) inCode = false
- if (!inCode && eligible && prevBlank && indented && !blank && !inList) inCode = true
- if (!blank) inList = LIST_ITEM_RE.test(line) || (inList && indented)
+ if (
+ !inCode &&
+ eligible &&
+ prevBlank &&
+ indented &&
+ !blank &&
+ (!inList || listContentIndent === null || indent >= listContentIndent + 4)
+ ) inCode = true
+ if (!blank) {
+ if (listItem) listContentIndent = line.match(LIST_ITEM_RE)![0].length
+ inList =
+ listItem ||
+ (inList && indented && listContentIndent !== null && indent < listContentIndent + 4)
+ }
prevBlank = blank
return !inCode
@@
prevBlank = true
inList = false
+ listContentIndent = null
inCode = falsediff --git a/autobot-frontend/src/utils/__tests__/ttsSpeechShaping.test.ts b/autobot-frontend/src/utils/__tests__/ttsSpeechShaping.test.ts
@@
it('keeps indented list content, nested items included (#17953)', () => {
expect(shape('- item\n\n - nested item\n second para')).toBe('item nested item second para')
})
+ it('drops indented code nested inside a list item (#17953)', () => {
+ expect(shape('- item\n\n secret()\n\nAfter.')).toBe('item After.')
+ })
+
Thinking Path
#13103. In a streamed reply, the first audio waited for a terminated sentence of 20+ chars: "long pause, then a wall of speech". The fix releases only the opening chunk on a weaker signal and keeps the standard rule for everything after it.
The issue states the real risk: a too-short opening can render in a noticeably different timbre on some TTS models. So the thresholds are named constants, and this PR stays a draft until the owner's listening test against our TTS worker sets them. Per the issue, if the artifact cannot be avoided at a useful length, this closes as not-worth-doing with the measurement recorded.
#17953's remaining criterion (indented code blocks read aloud) lives in the same file, so it rides here.
~~~fences already landed in #17954.Merge-order note:
ChatInterface.vueis grandfathered by the upcoming frontend file-size ratchet, and this PR may land after it. So the chunking moved intoutils/ttsSentences.tsandChatInterface.vueshrinks (1527 → 1522). The change is valid in either order; after the ratchet, both anchors are lowered to match.What Changed
utils/ttsSentences.ts:extractOpeningChunk(text)releases the opening at the first clause boundary (, ; : — –) pastOPENING_CLAUSE_MIN_CHARS(16), or else at a word boundary onceOPENING_WORD_MIN_CHARS(48) is reached. It returns null whenever any sentence has terminated, so it never jumps ahead of a short sentence held for merging. It never cuts inside a markdown link.createTtsStream(alreadyOpened)is per-reply chunking: the opening once, thenextractCompleteSentencesunchanged. A reply already under way (voice enabled mid-reply, or history) never gets an opening.MIN_TTS_SENTENCE_CHARS(20) moved here fromChatInterface.vue, next to the logic it governs.components/chat/ChatInterface.vue: the watcher asks_ttsStream.next(newText). The stream is reset alongside_speechShaperper reply, and as already-opened when the cursor is primed.utils/__tests__/ttsFastStart.test.ts: the clause/word fallback/too-early/too-short cases, suppression by a held short sentence, no cut inside a link, standard chunks after the opening, no fast start for a reply under way, and exact cursor accounting across 1/3/7/50-char deltas.utils/__tests__/ttsSpeechShaping.test.ts: indented code after a blank line, paragraph continuation kept, nested list content kept, streamed indented blocks, and line starts split across slices.Refs #13103 (code criteria delivered; the listening-test criterion needs the owner)
Closes #17953
Single-issue rationale: this PR delivers #13103's code criteria together with #17953 (same file). #13103 is linked with Refs only because its last criterion is the owner's listening test, which no code change can satisfy.
Verification
23b0d87662:vue-tsc (autobot-frontend): 0 errors,vitest (autobot-frontend): all relevant tests pass,pytest: all relevant tests pass.code-reviewer: the fast-start logic and cursor accounting hold. One high finding (a slice ending in\nmade the next line look like it followed a blank line, so indented prose was dropped) and two mediums (a line cut mid-indent or after a list marker; an opening cut inside a link) are fixed, with regression tests."e.g. "or a leading"1. "delays the opening until a standard sentence. Both only lose the early start; neither breaks anything.OPENING_CLAUSE_MIN_CHARS/OPENING_WORD_MIN_CHARSand record the chosen values on Voice: first audio waits for a full terminated sentence — no sub-sentence fast start #13103.Model Used
Claude Opus 5.5 (
claude-opus-5-5); review subagent on Sonnet.🤖 Generated with Claude Code
Summary by CodeRabbit