klaudio 0.8.1 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/player.js +18 -3
- package/src/tts.js +33 -15
package/package.json
CHANGED
package/src/player.js
CHANGED
|
@@ -392,13 +392,28 @@ export async function handlePlayCommand(args) {
|
|
|
392
392
|
|
|
393
393
|
// TTS: speak first 1-2 sentences of last_assistant_message
|
|
394
394
|
if (tts && hookData.last_assistant_message) {
|
|
395
|
-
|
|
396
|
-
|
|
395
|
+
// Strip markdown syntax and extract first sentence
|
|
396
|
+
const msg = hookData.last_assistant_message
|
|
397
|
+
.replace(/```[\s\S]*?```/g, "") // remove code blocks
|
|
398
|
+
.replace(/`([^`]+)`/g, "$1") // inline code -> text
|
|
399
|
+
.replace(/\*\*([^*]+)\*\*/g, "$1") // **bold** -> text
|
|
400
|
+
.replace(/\*([^*]+)\*/g, "$1") // *italic* -> text
|
|
401
|
+
.replace(/__([^_]+)__/g, "$1") // __bold__ -> text
|
|
402
|
+
.replace(/_([^_]+)_/g, "$1") // _italic_ -> text
|
|
403
|
+
.replace(/#{1,6}\s+/g, "") // headings
|
|
404
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") // [links](url) -> text
|
|
405
|
+
.replace(/^\s*[-*+]\s+/gm, "") // list bullets
|
|
406
|
+
.replace(/^\s*\d+\.\s+/gm, "") // numbered lists
|
|
407
|
+
.replace(/\n+/g, " ") // newlines -> spaces
|
|
408
|
+
.trim();
|
|
397
409
|
const sentences = msg.match(/[^.!?]*[.!?]/g);
|
|
398
410
|
const summary = sentences ? sentences[0].trim() : msg.slice(0, 100);
|
|
411
|
+
// Prefix with project folder name if available
|
|
412
|
+
const project = hookData.cwd ? hookData.cwd.replace(/\\/g, "/").split("/").pop() : null;
|
|
413
|
+
const spoken = project ? `${project}: ${summary}` : summary;
|
|
399
414
|
await soundPromise;
|
|
400
415
|
const { speak } = await import("./tts.js");
|
|
401
|
-
await speak(
|
|
416
|
+
await speak(spoken);
|
|
402
417
|
} else {
|
|
403
418
|
await soundPromise;
|
|
404
419
|
}
|
package/src/tts.js
CHANGED
|
@@ -179,13 +179,27 @@ export async function ensureVoiceModel(onProgress) {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
/**
|
|
182
|
-
* Speak text using
|
|
182
|
+
* Speak text using macOS `say` command (built-in, good quality).
|
|
183
|
+
*/
|
|
184
|
+
function speakMacOS(text) {
|
|
185
|
+
return new Promise((resolve) => {
|
|
186
|
+
execFile("say", ["-v", "Daniel", text], { timeout: 15000 }, () => resolve());
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Speak text using Piper TTS, with macOS `say` fallback.
|
|
183
192
|
* Auto-downloads piper and voice model on first use.
|
|
184
193
|
* Returns a promise that resolves when speech is done.
|
|
185
194
|
*/
|
|
186
195
|
export async function speak(text, onProgress) {
|
|
187
196
|
if (!text) return;
|
|
188
197
|
|
|
198
|
+
// macOS: use built-in `say` — better compatibility, no dylib issues
|
|
199
|
+
if (platform() === "darwin") {
|
|
200
|
+
return speakMacOS(text);
|
|
201
|
+
}
|
|
202
|
+
|
|
189
203
|
let piperBin, modelPath;
|
|
190
204
|
try {
|
|
191
205
|
[piperBin, modelPath] = await Promise.all([
|
|
@@ -201,20 +215,24 @@ export async function speak(text, onProgress) {
|
|
|
201
215
|
const hash = createHash("md5").update(text).digest("hex").slice(0, 8);
|
|
202
216
|
const outPath = join(tmpdir(), `klaudio-tts-${hash}.wav`);
|
|
203
217
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
218
|
+
try {
|
|
219
|
+
await new Promise((resolve, reject) => {
|
|
220
|
+
const child = execFile(piperBin, [
|
|
221
|
+
"--model", modelPath,
|
|
222
|
+
"--output_file", outPath,
|
|
223
|
+
], { windowsHide: true, timeout: 15000 }, (err) => {
|
|
224
|
+
if (err) reject(err);
|
|
225
|
+
else resolve();
|
|
226
|
+
});
|
|
227
|
+
// Feed text via stdin
|
|
228
|
+
child.stdin.write(text);
|
|
229
|
+
child.stdin.end();
|
|
211
230
|
});
|
|
212
|
-
// Feed text via stdin
|
|
213
|
-
child.stdin.write(text);
|
|
214
|
-
child.stdin.end();
|
|
215
|
-
});
|
|
216
231
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
232
|
+
// Play the generated wav
|
|
233
|
+
const { playSoundWithCancel } = await import("./player.js");
|
|
234
|
+
await playSoundWithCancel(outPath, { maxSeconds: 0 }).promise.catch(() => {});
|
|
235
|
+
} catch {
|
|
236
|
+
// Piper failed (dylib error, etc.) — skip silently
|
|
237
|
+
}
|
|
220
238
|
}
|