andito HF Staff commited on
Commit
2d6bb64
·
1 Parent(s): 74c42e9

Greet users when voice sessions start

Browse files
Files changed (2) hide show
  1. main.js +4 -0
  2. 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
- // Acknowledged by server, nothing to do.
 
 
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