voice: quieter thinking tone, log cleanup, and small ui polish

- Add scripts/lower-thinking-tone.mjs to scale the thinking-tone PCM
  amplitude in-place; apply it at 0.1 so the cue is a soft indicator
  instead of triggering the VAD via mic feedback.
- Drop the "Received first voice_audio_chunk" log that re-fires every
  summary window because the chunk counter is reset.
- Match Spoke message icon and label sizing to the standard tool-call
  badge.
- Append a spoken-input instruction so voice replies route through the
  speak tool.
This commit is contained in:
Mohamed Boudra
2026-05-05 19:56:51 +07:00
parent 91865ba66e
commit e22b497a08
5 changed files with 62 additions and 14 deletions
@@ -0,0 +1,57 @@
#!/usr/bin/env node
// Scale the amplitude of the thinking-tone PCM16 base64 in-place.
// Usage: node scripts/lower-thinking-tone.mjs <gain>
// Example: node scripts/lower-thinking-tone.mjs 0.3 (≈ -10 dB)
import { readFileSync, writeFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
const here = dirname(fileURLToPath(import.meta.url));
const target = resolve(here, "../src/utils/thinking-tone.native-pcm.ts");
const gainArg = process.argv[2];
if (!gainArg) {
console.error("Usage: node scripts/lower-thinking-tone.mjs <gain>");
process.exit(1);
}
const gain = Number(gainArg);
if (!Number.isFinite(gain) || gain < 0) {
console.error(`Invalid gain: ${gainArg}`);
process.exit(1);
}
const source = readFileSync(target, "utf8");
const match = source.match(/"([A-Za-z0-9+/=]+)"/);
if (!match) {
console.error("Could not find base64 string in target file.");
process.exit(1);
}
const originalBase64 = match[1];
const buf = Buffer.from(originalBase64, "base64");
if (buf.byteLength % 2 !== 0) {
console.error(`PCM16 buffer has odd length ${buf.byteLength}`);
process.exit(1);
}
let peakBefore = 0;
let peakAfter = 0;
const out = Buffer.alloc(buf.byteLength);
for (let i = 0; i < buf.byteLength; i += 2) {
const sample = buf.readInt16LE(i);
peakBefore = Math.max(peakBefore, Math.abs(sample));
const scaled = Math.max(-32768, Math.min(32767, Math.round(sample * gain)));
peakAfter = Math.max(peakAfter, Math.abs(scaled));
out.writeInt16LE(scaled, i);
}
const newBase64 = out.toString("base64");
const updated = source.replace(originalBase64, newBase64);
writeFileSync(target, updated);
console.log(`Scaled thinking tone by ${gain}.`);
console.log(` samples: ${buf.byteLength / 2}`);
console.log(` peak abs: ${peakBefore} -> ${peakAfter}`);
console.log(` base64 len ${originalBase64.length} -> ${newBase64.length}`);
console.log(` wrote ${target}`);
+3 -3
View File
@@ -1740,8 +1740,8 @@ const speakMessageStylesheet = StyleSheet.create((theme) => ({
},
headerLabel: {
fontFamily: Fonts.sans,
fontSize: 12,
fontWeight: "500",
fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.normal,
color: theme.colors.foregroundMuted,
},
text: {
@@ -1769,7 +1769,7 @@ export const SpeakMessage = memo(function SpeakMessage({
return (
<View testID="speak-message" style={containerStyle}>
<View style={speakMessageStylesheet.header}>
<ThemedMicVocal size={14} uniProps={foregroundMutedColorMapping} />
<ThemedMicVocal size={12} uniProps={foregroundMutedColorMapping} />
<Text style={speakMessageStylesheet.headerLabel}>Spoke</Text>
</View>
<Text style={speakMessageStylesheet.text}>{message}</Text>
File diff suppressed because one or more lines are too long
-9
View File
@@ -7546,15 +7546,6 @@ export class Session {
const chunkBytes = Buffer.byteLength(msg.audio, "base64");
this.voiceInputChunkCount += 1;
this.voiceInputBytes += chunkBytes;
if (this.voiceInputChunkCount === 1) {
this.sessionLogger.info(
{
format: chunkFormat,
audioBytes: chunkBytes,
},
"Received first voice_audio_chunk for active voice mode",
);
}
const now = Date.now();
if (this.voiceInputChunkCount % 50 === 0 || now - this.voiceInputWindowStartedAt >= 1000) {
this.sessionLogger.info(
+1 -1
View File
@@ -54,7 +54,7 @@ export function buildVoiceModeSystemPrompt(existing: string | undefined, enabled
}
export function wrapSpokenInput(text: string): string {
return `<spoken-input>\n${text}\n</spoken-input>`;
return `<spoken-input>\n${text}\n</spoken-input>\n<instruction>This message was spoken by the user. Respond using the speak tool only, not normal messages, because the user may not be looking at the chat.</instruction>`;
}
export function buildVoiceAgentMcpServerConfig(params: {