Greet users when voice sessions start
Browse files- main.js +4 -0
- ws/s2s-ws-client.js +27 -1
main.js
CHANGED
|
@@ -25,6 +25,9 @@ const DEFAULT_VOICE = "Aiden";
|
|
| 25 |
const DEFAULT_INSTRUCTIONS =
|
| 26 |
"You are a friendly voice assistant. " +
|
| 27 |
"Keep replies short, warm, and spoken. Avoid long monologues.";
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
// Appended to the user's instructions whenever at least one tool is enabled.
|
| 30 |
// Stops the model from announcing capabilities ("Yes, I can search") and then
|
|
@@ -1183,6 +1186,7 @@ async function doStart(audioContext = null) {
|
|
| 1183 |
...target,
|
| 1184 |
voice: settings.voice,
|
| 1185 |
instructions: effectiveInstructions(),
|
|
|
|
| 1186 |
acquireMic: acquireMicStream,
|
| 1187 |
tools: activeToolDefs(),
|
| 1188 |
noiseGate: gateParams(settings.noiseGate),
|
|
|
|
| 25 |
const DEFAULT_INSTRUCTIONS =
|
| 26 |
"You are a friendly voice assistant. " +
|
| 27 |
"Keep replies short, warm, and spoken. Avoid long monologues.";
|
| 28 |
+
const STARTUP_GREETING_PROMPT =
|
| 29 |
+
"Start the conversation now with a brief, spontaneous greeting in character. " +
|
| 30 |
+
"Keep it to one sentence, invite the user in naturally, and vary the wording each time.";
|
| 31 |
|
| 32 |
// Appended to the user's instructions whenever at least one tool is enabled.
|
| 33 |
// Stops the model from announcing capabilities ("Yes, I can search") and then
|
|
|
|
| 1186 |
...target,
|
| 1187 |
voice: settings.voice,
|
| 1188 |
instructions: effectiveInstructions(),
|
| 1189 |
+
startupGreeting: STARTUP_GREETING_PROMPT,
|
| 1190 |
acquireMic: acquireMicStream,
|
| 1191 |
tools: activeToolDefs(),
|
| 1192 |
noiseGate: gateParams(settings.noiseGate),
|
ws/s2s-ws-client.js
CHANGED
|
@@ -51,6 +51,8 @@
|
|
| 51 |
* session POST and dials it directly — no load balancer in between.
|
| 52 |
* @property {string} voice
|
| 53 |
* @property {string} instructions
|
|
|
|
|
|
|
| 54 |
* @property {MediaStream} [micStream] Live mic stream. Provide this OR `acquireMic`.
|
| 55 |
* @property {() => Promise<MediaStream>} [acquireMic] Lazily obtain the mic stream,
|
| 56 |
* called only once a session is actually granted (after any queue wait). Lets the
|
|
@@ -198,6 +200,8 @@ export class S2sWsRealtimeClient extends EventTarget {
|
|
| 198 |
/** @type {Promise<void> | null} */
|
| 199 |
this._readyPromise = null;
|
| 200 |
this._sessionConfigured = false;
|
|
|
|
|
|
|
| 201 |
this._debug = (() => { try { return localStorage.getItem("s2s.debug") === "1"; } catch { return false; } })();
|
| 202 |
}
|
| 203 |
|
|
@@ -629,7 +633,9 @@ export class S2sWsRealtimeClient extends EventTarget {
|
|
| 629 |
break;
|
| 630 |
|
| 631 |
case "session.updated":
|
| 632 |
-
//
|
|
|
|
|
|
|
| 633 |
break;
|
| 634 |
|
| 635 |
case "input_audio_buffer.speech_started":
|
|
@@ -965,6 +971,26 @@ export class S2sWsRealtimeClient extends EventTarget {
|
|
| 965 |
});
|
| 966 |
}
|
| 967 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 968 |
/**
|
| 969 |
* Ask the model to generate a response now (after feeding tool results).
|
| 970 |
* Serialized: if a response is already in flight we queue this request and
|
|
|
|
| 51 |
* session POST and dials it directly — no load balancer in between.
|
| 52 |
* @property {string} voice
|
| 53 |
* @property {string} instructions
|
| 54 |
+
* @property {string} [startupGreeting] Hidden user prompt that asks the model
|
| 55 |
+
* to greet once after the initial session configuration is acknowledged.
|
| 56 |
* @property {MediaStream} [micStream] Live mic stream. Provide this OR `acquireMic`.
|
| 57 |
* @property {() => Promise<MediaStream>} [acquireMic] Lazily obtain the mic stream,
|
| 58 |
* called only once a session is actually granted (after any queue wait). Lets the
|
|
|
|
| 200 |
/** @type {Promise<void> | null} */
|
| 201 |
this._readyPromise = null;
|
| 202 |
this._sessionConfigured = false;
|
| 203 |
+
this._startupGreeting = options.startupGreeting?.trim() ?? "";
|
| 204 |
+
this._startupGreetingSent = false;
|
| 205 |
this._debug = (() => { try { return localStorage.getItem("s2s.debug") === "1"; } catch { return false; } })();
|
| 206 |
}
|
| 207 |
|
|
|
|
| 633 |
break;
|
| 634 |
|
| 635 |
case "session.updated":
|
| 636 |
+
// Wait for this acknowledgement so the real system instructions are in
|
| 637 |
+
// place before the greeting request warms their prompt prefix.
|
| 638 |
+
this._sendStartupGreeting();
|
| 639 |
break;
|
| 640 |
|
| 641 |
case "input_audio_buffer.speech_started":
|
|
|
|
| 971 |
});
|
| 972 |
}
|
| 973 |
|
| 974 |
+
/**
|
| 975 |
+
* Ask the model to open the conversation exactly once. The synthetic user
|
| 976 |
+
* prompt stays in conversation history, so the greeting warms the actual
|
| 977 |
+
* system-prompt prefix that the first spoken turn will reuse.
|
| 978 |
+
*/
|
| 979 |
+
_sendStartupGreeting() {
|
| 980 |
+
if (!this._startupGreeting || this._startupGreetingSent) return;
|
| 981 |
+
this._startupGreetingSent = true;
|
| 982 |
+
this._send({
|
| 983 |
+
type: "conversation.item.create",
|
| 984 |
+
item: {
|
| 985 |
+
type: "message",
|
| 986 |
+
role: "user",
|
| 987 |
+
content: [{ type: "input_text", text: this._startupGreeting }],
|
| 988 |
+
},
|
| 989 |
+
});
|
| 990 |
+
this.requestResponse();
|
| 991 |
+
if (this._debug) console.debug("[ws] startup greeting queued");
|
| 992 |
+
}
|
| 993 |
+
|
| 994 |
/**
|
| 995 |
* Ask the model to generate a response now (after feeding tool results).
|
| 996 |
* Serialized: if a response is already in flight we queue this request and
|