diff --git a/package-lock.json b/package-lock.json index 744ce85..43781fa 100644 --- a/package-lock.json +++ b/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", diff --git a/package.json b/package.json index 7d9d6d3..ab18a7c 100644 --- a/package.json +++ b/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", diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 15aa32a..1ce56ae 100644 --- a/src/core/webview/ClineProvider.ts +++ b/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, this.getGlobalState("apiModelId") as Promise, @@ -1082,6 +1092,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.getGlobalState("allowedCommands") as Promise, this.getGlobalState("soundEnabled") as Promise, this.getGlobalState("diffEnabled") as Promise, + this.getGlobalState("soundVolume") as Promise, ]) let apiProvider: ApiProvider @@ -1137,6 +1148,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { allowedCommands, soundEnabled, diffEnabled, + soundVolume, } } diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 608b5e5..07a3dde 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -51,6 +51,7 @@ export interface ExtensionState { uriScheme?: string allowedCommands?: string[] soundEnabled?: boolean + soundVolume?: number diffEnabled?: boolean } diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 31802b9..2864a94 100644 --- a/src/shared/WebviewMessage.ts +++ b/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 diff --git a/src/utils/sound.ts b/src/utils/sound.ts index 9255db4..a7f0d73 100644 --- a/src/utils/sound.ts +++ b/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 diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index deab3d6..f4e2da9 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/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) => {

Experimental Features

- setSoundEnabled(e.target.checked)}> - Enable sound effects - -

- When enabled, Cline will play sound effects for notifications and events. -

+
+ setSoundEnabled(e.target.checked)}> + Enable sound effects + +

+ When enabled, Cline will play sound effects for notifications and events. +

+
+ {soundEnabled && ( +
+
+ Volume + setSoundVolume(parseFloat(e.target.value))} + style={{ + flexGrow: 1, + accentColor: 'var(--vscode-button-background)', + height: '2px' + }} + /> + + {Math.round((soundVolume ?? 0.5) * 100)}% + +
+
+ )}
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index f9690b6..c8307ff 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/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 })), }