Skip to content

Commit a15691d

Browse files
authored
Add a way to stop TTS (#1787)
1 parent f4abdf4 commit a15691d

File tree

5 files changed

+119
-88
lines changed

5 files changed

+119
-88
lines changed

src/core/webview/ClineProvider.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import { BrowserSession } from "../../services/browser/BrowserSession"
4141
import { discoverChromeInstances } from "../../services/browser/browserDiscovery"
4242
import { fileExistsAtPath } from "../../utils/fs"
4343
import { playSound, setSoundEnabled, setSoundVolume } from "../../utils/sound"
44-
import { playTts, setTtsEnabled, setTtsSpeed } from "../../utils/tts"
44+
import { playTts, setTtsEnabled, setTtsSpeed, stopTts } from "../../utils/tts"
4545
import { singleCompletionHandler } from "../../utils/single-completion-handler"
4646
import { searchCommits } from "../../utils/git"
4747
import { getDiffStrategy } from "../diff/DiffStrategy"
@@ -1281,9 +1281,15 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
12811281
break
12821282
case "playTts":
12831283
if (message.text) {
1284-
playTts(message.text)
1284+
playTts(message.text, {
1285+
onStart: () => this.postMessageToWebview({ type: "ttsStart", text: message.text }),
1286+
onStop: () => this.postMessageToWebview({ type: "ttsStop", text: message.text }),
1287+
})
12851288
}
12861289
break
1290+
case "stopTts":
1291+
stopTts()
1292+
break
12871293
case "diffEnabled":
12881294
const diffEnabled = message.bool ?? true
12891295
await this.updateGlobalState("diffEnabled", diffEnabled)

src/shared/ExtensionMessage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export interface ExtensionMessage {
5454
| "browserToolEnabled"
5555
| "browserConnectionResult"
5656
| "remoteBrowserEnabled"
57+
| "ttsStart"
58+
| "ttsStop"
5759
text?: string
5860
action?:
5961
| "chatButtonClicked"

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export interface WebviewMessage {
5252
| "alwaysAllowSubtasks"
5353
| "playSound"
5454
| "playTts"
55+
| "stopTts"
5556
| "soundEnabled"
5657
| "ttsEnabled"
5758
| "ttsSpeed"

src/utils/tts.ts

Lines changed: 55 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,81 @@
1-
import * as vscode from "vscode"
1+
interface Say {
2+
speak: (text: string, voice?: string, speed?: number, callback?: (err?: string) => void) => void
3+
stop: () => void
4+
}
5+
6+
type PlayTtsOptions = {
7+
onStart?: () => void
8+
onStop?: () => void
9+
}
10+
11+
type QueueItem = {
12+
message: string
13+
options: PlayTtsOptions
14+
}
215

316
let isTtsEnabled = false
17+
18+
export const setTtsEnabled = (enabled: boolean) => (isTtsEnabled = enabled)
19+
420
let speed = 1.0
5-
let isSpeaking = false
6-
const utteranceQueue: string[] = []
7-
8-
/**
9-
* Set tts configuration
10-
* @param enabled boolean
11-
*/
12-
export const setTtsEnabled = (enabled: boolean): void => {
13-
isTtsEnabled = enabled
21+
22+
export const setTtsSpeed = (newSpeed: number) => (speed = newSpeed)
23+
24+
let sayInstance: Say | undefined = undefined
25+
let queue: QueueItem[] = []
26+
27+
export const playTts = async (message: string, options: PlayTtsOptions = {}) => {
28+
if (!isTtsEnabled) {
29+
return
30+
}
31+
32+
try {
33+
queue.push({ message, options })
34+
await processQueue()
35+
} catch (error) {}
1436
}
1537

16-
/**
17-
* Set tts speed
18-
* @param speed number
19-
*/
20-
export const setTtsSpeed = (newSpeed: number): void => {
21-
speed = newSpeed
38+
export const stopTts = () => {
39+
sayInstance?.stop()
40+
sayInstance = undefined
41+
queue = []
2242
}
2343

24-
/**
25-
* Process the next item in the utterance queue
26-
*/
2744
const processQueue = async (): Promise<void> => {
28-
if (!isTtsEnabled || isSpeaking || utteranceQueue.length === 0) {
45+
if (!isTtsEnabled || sayInstance) {
46+
return
47+
}
48+
49+
const item = queue.shift()
50+
51+
if (!item) {
2952
return
3053
}
3154

3255
try {
33-
isSpeaking = true
34-
const nextUtterance = utteranceQueue.shift()!
35-
const say = require("say")
56+
const { message: nextUtterance, options } = item
3657

37-
// Wrap say.speak in a promise to handle completion
3858
await new Promise<void>((resolve, reject) => {
39-
say.speak(nextUtterance, null, speed, (err: Error) => {
59+
const say: Say = require("say")
60+
sayInstance = say
61+
options.onStart?.()
62+
63+
say.speak(nextUtterance, undefined, speed, (err) => {
64+
options.onStop?.()
65+
4066
if (err) {
41-
reject(err)
67+
reject(new Error(err))
4268
} else {
4369
resolve()
4470
}
71+
72+
sayInstance = undefined
4573
})
4674
})
4775

48-
isSpeaking = false
49-
// Process next item in queue if any
5076
await processQueue()
5177
} catch (error: any) {
52-
isSpeaking = false
53-
//vscode.window.showErrorMessage(error.message)
54-
// Try to continue with next item despite error
55-
await processQueue()
56-
}
57-
}
58-
59-
/**
60-
* Queue a tts message to be spoken
61-
* @param message string
62-
* @return void
63-
*/
64-
export const playTts = async (message: string): Promise<void> => {
65-
if (!isTtsEnabled) {
66-
return
67-
}
68-
69-
try {
70-
utteranceQueue.push(message)
78+
sayInstance = undefined
7179
await processQueue()
72-
} catch (error: any) {
73-
//vscode.window.showErrorMessage(error.message)
7480
}
7581
}

0 commit comments

Comments
 (0)