Browse Source

add volume setting

Justin Quan 1 year ago
parent
commit
1adc36a292

+ 6 - 0
package-lock.json

@@ -38,6 +38,7 @@
         "puppeteer-chromium-resolver": "^23.0.0",
         "puppeteer-core": "^23.4.0",
         "serialize-error": "^11.0.3",
+        "sound-play": "^1.1.0",
         "strip-ansi": "^7.1.0",
         "tree-sitter-wasms": "^0.1.11",
         "turndown": "^7.2.0",
@@ -14001,6 +14002,11 @@
         "node": ">= 14"
       }
     },
+    "node_modules/sound-play": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/sound-play/-/sound-play-1.1.0.tgz",
+      "integrity": "sha512-Bd/L0AoCwITFeOnpNLMsfPXrV5GG5NhrC/T6odveahYbhPZkdTnrFXRia9FCC5WBWdUTw1d+yvLBvi4wnD1xOA=="
+    },
     "node_modules/source-map": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",

+ 1 - 0
package.json

@@ -220,6 +220,7 @@
     "puppeteer-chromium-resolver": "^23.0.0",
     "puppeteer-core": "^23.4.0",
     "serialize-error": "^11.0.3",
+    "sound-play": "^1.1.0",
     "strip-ansi": "^7.1.0",
     "tree-sitter-wasms": "^0.1.11",
     "turndown": "^7.2.0",

+ 13 - 1
src/core/webview/ClineProvider.ts

@@ -22,7 +22,7 @@ import { Cline } from "../Cline"
 import { openMention } from "../mentions"
 import { getNonce } from "./getNonce"
 import { getUri } from "./getUri"
-import { playSound, setSoundEnabled } from "../../utils/sound"
+import { playSound, setSoundEnabled, setSoundVolume } from "../../utils/sound"
 
 /*
 https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -66,6 +66,7 @@ type GlobalStateKey =
 	| "openRouterUseMiddleOutTransform"
 	| "allowedCommands"
 	| "soundEnabled"
+	| "soundVolume"
 	| "diffEnabled"
 	| "alwaysAllowMcp"
 
@@ -597,6 +598,12 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 						setSoundEnabled(soundEnabled)  // Add this line to update the sound utility
 						await this.postStateToWebview()
 						break
+					case "soundVolume":
+						const soundVolume = message.value ?? 0.5
+						await this.updateGlobalState("soundVolume", soundVolume)
+						setSoundVolume(soundVolume)
+						await this.postStateToWebview()
+						break
 					case "diffEnabled":
 						const diffEnabled = message.bool ?? true
 						await this.updateGlobalState("diffEnabled", diffEnabled)
@@ -929,6 +936,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			soundEnabled,
 			diffEnabled,
 			taskHistory,
+			soundVolume,
 		} = await this.getState()
 		
 		const allowedCommands = vscode.workspace
@@ -953,6 +961,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			diffEnabled: diffEnabled ?? false,
 			shouldShowAnnouncement: lastShownAnnouncementId !== this.latestAnnouncementId,
 			allowedCommands,
+			soundVolume: soundVolume ?? 0.5,
 		}
 	}
 
@@ -1045,6 +1054,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			allowedCommands,
 			soundEnabled,
 			diffEnabled,
+			soundVolume,
 		] = await Promise.all([
 			this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
 			this.getGlobalState("apiModelId") as Promise<string | undefined>,
@@ -1082,6 +1092,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			this.getGlobalState("allowedCommands") as Promise<string[] | undefined>,
 			this.getGlobalState("soundEnabled") as Promise<boolean | undefined>,
 			this.getGlobalState("diffEnabled") as Promise<boolean | undefined>,
+			this.getGlobalState("soundVolume") as Promise<number | undefined>,
 		])
 
 		let apiProvider: ApiProvider
@@ -1137,6 +1148,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			allowedCommands,
 			soundEnabled,
 			diffEnabled,
+			soundVolume,
 		}
 	}
 

+ 1 - 0
src/shared/ExtensionMessage.ts

@@ -51,6 +51,7 @@ export interface ExtensionState {
 	uriScheme?: string
 	allowedCommands?: string[]
 	soundEnabled?: boolean
+	soundVolume?: number
 	diffEnabled?: boolean
 }
 

+ 2 - 0
src/shared/WebviewMessage.ts

@@ -32,6 +32,7 @@ export interface WebviewMessage {
 		| "alwaysAllowMcp"
 		| "playSound"
 		| "soundEnabled"
+		| "soundVolume"
 		| "diffEnabled"
 		| "openMcpSettings"
 		| "restartMcpServer"
@@ -43,6 +44,7 @@ export interface WebviewMessage {
 	apiConfiguration?: ApiConfiguration
 	images?: string[]
 	bool?: boolean
+	value?: number
 	commands?: string[]
 	audioType?: AudioType
 	// For toggleToolAutoApprove

+ 12 - 5
src/utils/sound.ts

@@ -21,6 +21,7 @@ export const isWAV = (filepath: string): boolean => {
 }
 
 let isSoundEnabled = false
+let volume = .5
 
 /**
  * Set sound configuration
@@ -30,6 +31,14 @@ export const setSoundEnabled = (enabled: boolean): void => {
 	isSoundEnabled = enabled
 }
 
+/**
+ * Set sound volume
+ * @param volume number
+ */
+export const setSoundVolume = (newVolume: number): void => {
+	volume = newVolume
+}
+
 /**
  * Play a sound file
  * @param filepath string
@@ -54,11 +63,9 @@ export const playSound = (filepath: string): void => {
 			return // Skip playback within minimum interval to prevent continuous playback
 		}
 
-		const player = require("play-sound")()
-		player.play(filepath, function (err: any) {
-			if (err) {
-				throw new Error("Failed to play sound effect")
-			}
+		const sound = require("sound-play")
+		sound.play(filepath, volume).catch(() => {
+			throw new Error("Failed to play sound effect")
 		})
 
 		lastPlayedTime = currentTime

+ 39 - 11
webview-ui/src/components/settings/SettingsView.tsx

@@ -29,6 +29,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
 		setAlwaysAllowMcp,
 		soundEnabled,
 		setSoundEnabled,
+		soundVolume,
+		setSoundVolume,
 		diffEnabled,
 		setDiffEnabled,
 		openRouterModels,
@@ -55,6 +57,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
 			vscode.postMessage({ type: "alwaysAllowMcp", bool: alwaysAllowMcp })
 			vscode.postMessage({ type: "allowedCommands", commands: allowedCommands ?? [] })
 			vscode.postMessage({ type: "soundEnabled", bool: soundEnabled })
+			vscode.postMessage({ type: "soundVolume", value: soundVolume })
 			vscode.postMessage({ type: "diffEnabled", bool: diffEnabled })
 			onDone()
 		}
@@ -306,17 +309,42 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
 					<h4 style={{ fontWeight: 500, marginBottom: 10 }}>Experimental Features</h4>
 
 					<div style={{ marginBottom: 5 }}>
-						<VSCodeCheckbox checked={soundEnabled} onChange={(e: any) => setSoundEnabled(e.target.checked)}>
-							<span style={{ fontWeight: "500" }}>Enable sound effects</span>
-						</VSCodeCheckbox>
-						<p
-							style={{
-								fontSize: "12px",
-								marginTop: "5px",
-								color: "var(--vscode-descriptionForeground)",
-							}}>
-							When enabled, Cline will play sound effects for notifications and events.
-						</p>
+						<div style={{ marginBottom: 10 }}>
+							<VSCodeCheckbox checked={soundEnabled} onChange={(e: any) => setSoundEnabled(e.target.checked)}>
+								<span style={{ fontWeight: "500" }}>Enable sound effects</span>
+							</VSCodeCheckbox>
+							<p
+								style={{
+									fontSize: "12px",
+									marginTop: "5px",
+									color: "var(--vscode-descriptionForeground)",
+								}}>
+								When enabled, Cline will play sound effects for notifications and events.
+							</p>
+						</div>
+						{soundEnabled && (
+							<div style={{ marginLeft: 0 }}>
+								<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
+									<span style={{ fontWeight: "500", minWidth: '50px' }}>Volume</span>
+									<input
+										type="range"
+										min="0"
+										max="1"
+										step="0.01"
+										value={soundVolume ?? 0.5}
+										onChange={(e) => setSoundVolume(parseFloat(e.target.value))}
+										style={{
+											flexGrow: 1,
+											accentColor: 'var(--vscode-button-background)',
+											height: '2px'
+										}}
+									/>
+									<span style={{ minWidth: '35px', textAlign: 'left' }}>
+										{Math.round((soundVolume ?? 0.5) * 100)}%
+									</span>
+								</div>
+							</div>
+						)}
 					</div>
 				</div>
 

+ 4 - 0
webview-ui/src/context/ExtensionStateContext.tsx

@@ -29,6 +29,7 @@ export interface ExtensionStateContextType extends ExtensionState {
 	setShowAnnouncement: (value: boolean) => void
 	setAllowedCommands: (value: string[]) => void
 	setSoundEnabled: (value: boolean) => void
+	setSoundVolume: (value: number) => void
 	setDiffEnabled: (value: boolean) => void
 }
 
@@ -42,6 +43,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		shouldShowAnnouncement: false,
 		allowedCommands: [],
 		soundEnabled: false,
+		soundVolume: 0.5,
 		diffEnabled: false,
 	})
 	const [didHydrateState, setDidHydrateState] = useState(false)
@@ -129,6 +131,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		openRouterModels,
 		mcpServers,
 		filePaths,
+		soundVolume: state.soundVolume,
 		setApiConfiguration: (value) => setState((prevState) => ({ ...prevState, apiConfiguration: value })),
 		setCustomInstructions: (value) => setState((prevState) => ({ ...prevState, customInstructions: value })),
 		setAlwaysAllowReadOnly: (value) => setState((prevState) => ({ ...prevState, alwaysAllowReadOnly: value })),
@@ -139,6 +142,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		setShowAnnouncement: (value) => setState((prevState) => ({ ...prevState, shouldShowAnnouncement: value })),
 		setAllowedCommands: (value) => setState((prevState) => ({ ...prevState, allowedCommands: value })),
 		setSoundEnabled: (value) => setState((prevState) => ({ ...prevState, soundEnabled: value })),
+		setSoundVolume: (value) => setState((prevState) => ({ ...prevState, soundVolume: value })),
 		setDiffEnabled: (value) => setState((prevState) => ({ ...prevState, diffEnabled: value })),
 	}