@xmov/avatar 2.0.0-alpha.53 → 2.0.0-alpha.54
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/dist/agent/avatar.cjs +1 -1
- package/dist/agent/avatar.cjs.map +1 -1
- package/dist/agent/avatar.modern.js +1 -1
- package/dist/agent/avatar.modern.js.map +1 -1
- package/dist/agent/avatar.module.js +1 -1
- package/dist/agent/avatar.module.js.map +1 -1
- package/dist/agent/avatar.umd.js +1 -1
- package/dist/agent/avatar.umd.js.map +1 -1
- package/dist/agent/index.d.ts +11 -2
- package/dist/index.cjs +1 -1
- package/dist/index.modern.js +1 -1
- package/dist/index.module.js +1 -1
- package/dist/index.umd.js +1 -1
- package/package.json +9 -2
- package/src/agent/__tests__/agent.test.ts +39 -11
- package/src/agent/index.ts +33 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmov/avatar",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.54",
|
|
4
4
|
"description": "魔珐数字人 JS-SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"source": "src/index.ts",
|
|
@@ -10,12 +10,19 @@
|
|
|
10
10
|
"default": "./dist/index.modern.js"
|
|
11
11
|
},
|
|
12
12
|
"./agent": {
|
|
13
|
-
"types": "./
|
|
13
|
+
"types": "./dist/agent/index.d.ts",
|
|
14
14
|
"require": "./dist/agent/index.cjs",
|
|
15
15
|
"default": "./dist/agent/avatar.modern.js"
|
|
16
16
|
},
|
|
17
17
|
"./agent/umd": "./dist/agent/index.umd.js"
|
|
18
18
|
},
|
|
19
|
+
"typesVersions": {
|
|
20
|
+
"*": {
|
|
21
|
+
"agent": [
|
|
22
|
+
"dist/agent/index.d.ts"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
19
26
|
"amdName": "XmovAvatar",
|
|
20
27
|
"main": "./dist/index.cjs",
|
|
21
28
|
"types": "./dist/index.d.ts",
|
|
@@ -2153,10 +2153,17 @@ test("XingyunAvatarAgent rejects ambiguous non-empty config sources", () => {
|
|
|
2153
2153
|
);
|
|
2154
2154
|
});
|
|
2155
2155
|
|
|
2156
|
-
test("XingyunAvatarAgent extends XmovAvatar
|
|
2156
|
+
test("XingyunAvatarAgent extends XmovAvatar while keeping the base class behavior unchanged", async () => {
|
|
2157
2157
|
MockWebSocket.instances = [];
|
|
2158
2158
|
const { agent } = createAgent();
|
|
2159
2159
|
const XmovAvatar = require("../../index");
|
|
2160
|
+
const avatar = new XmovAvatar({});
|
|
2161
|
+
|
|
2162
|
+
avatar.speak("legacy TTSA speak");
|
|
2163
|
+
const interruptedSpeechId = avatar.interrupt("speak");
|
|
2164
|
+
assert.deepEqual(avatar.speeches, [["legacy TTSA speak"]]);
|
|
2165
|
+
assert.deepEqual(avatar.interrupts, ["speak"]);
|
|
2166
|
+
assert.equal(interruptedSpeechId, 1);
|
|
2160
2167
|
|
|
2161
2168
|
await agent.init();
|
|
2162
2169
|
const ws = MockWebSocket.instances[0];
|
|
@@ -2167,11 +2174,6 @@ test("XingyunAvatarAgent extends XmovAvatar and preserves base speak", async ()
|
|
|
2167
2174
|
|
|
2168
2175
|
agent.start();
|
|
2169
2176
|
assert.equal(agent.startCalls, 1);
|
|
2170
|
-
agent.speak("legacy TTSA speak");
|
|
2171
|
-
assert.deepEqual(agent.speeches, [["legacy TTSA speak"]]);
|
|
2172
|
-
assert.deepEqual(ws.sent, []);
|
|
2173
|
-
agent.avatarOptions.onVoiceStateChange("end");
|
|
2174
|
-
assert.deepEqual(ws.sent, []);
|
|
2175
2177
|
});
|
|
2176
2178
|
|
|
2177
2179
|
test("XingyunAvatarAgent restarts TTSA after same-session ready without leaving stopped state", async () => {
|
|
@@ -2200,7 +2202,7 @@ test("XingyunAvatarAgent ignores an explicit start while init is pending", () =>
|
|
|
2200
2202
|
assert.equal(agent.started, false);
|
|
2201
2203
|
});
|
|
2202
2204
|
|
|
2203
|
-
test("XingyunAvatarAgent sends frozen ask and speak payloads without client IDs or providers", async () => {
|
|
2205
|
+
test("XingyunAvatarAgent sends frozen ask and canonical speak payloads without client IDs or providers", async () => {
|
|
2204
2206
|
MockWebSocket.instances = [];
|
|
2205
2207
|
const conversations = [];
|
|
2206
2208
|
const { agent } = createAgent({
|
|
@@ -2215,15 +2217,34 @@ test("XingyunAvatarAgent sends frozen ask and speak payloads without client IDs
|
|
|
2215
2217
|
assert.deepEqual(askPayload, { type: "ask", message: { text: "hello" } });
|
|
2216
2218
|
assert.deepEqual(conversations.at(-1), { state: "asking", text: "hello" });
|
|
2217
2219
|
|
|
2218
|
-
|
|
2220
|
+
const speakResult = agent.speak("welcome");
|
|
2221
|
+
assert.equal(speakResult, undefined);
|
|
2219
2222
|
const speakPayload = JSON.parse(ws.sent.at(-1));
|
|
2220
2223
|
assert.deepEqual(speakPayload, {
|
|
2221
2224
|
type: "speak",
|
|
2222
2225
|
message: { text: "welcome", is_start: true, is_end: true },
|
|
2223
2226
|
});
|
|
2227
|
+
assert.deepEqual(conversations.at(-1), { state: "speaking-directly", text: "welcome" });
|
|
2228
|
+
|
|
2229
|
+
const e2eSentCount = ws.sent.length;
|
|
2230
|
+
const replayExtra = { client_speak_id: "replay", client_frame: 7 };
|
|
2231
|
+
const replayResult = agent.speak("<speak>replay</speak>", false, true, replayExtra);
|
|
2232
|
+
assert.equal(replayResult, undefined);
|
|
2233
|
+
assert.deepEqual(agent.speeches.at(-1), ["<speak>replay</speak>", false, true, replayExtra]);
|
|
2234
|
+
assert.equal(ws.sent.length, e2eSentCount);
|
|
2235
|
+
agent.avatarOptions.onVoiceStateChange("end", 1200);
|
|
2236
|
+
assert.equal(ws.sent.length, e2eSentCount);
|
|
2237
|
+
|
|
2238
|
+
const sentCount = ws.sent.length;
|
|
2239
|
+
await agent.speakByE2E("deprecated welcome");
|
|
2240
|
+
assert.equal(ws.sent.length, sentCount + 1);
|
|
2241
|
+
assert.deepEqual(JSON.parse(ws.sent.at(-1)), {
|
|
2242
|
+
type: "speak",
|
|
2243
|
+
message: { text: "deprecated welcome", is_start: true, is_end: true },
|
|
2244
|
+
});
|
|
2224
2245
|
});
|
|
2225
2246
|
|
|
2226
|
-
test("XingyunAvatarAgent interrupts E2E and local playback
|
|
2247
|
+
test("XingyunAvatarAgent interrupts E2E and local playback through canonical and deprecated methods", async () => {
|
|
2227
2248
|
MockWebSocket.instances = [];
|
|
2228
2249
|
const { agent } = createAgent();
|
|
2229
2250
|
|
|
@@ -2231,9 +2252,16 @@ test("XingyunAvatarAgent interrupts E2E and local playback without client IDs",
|
|
|
2231
2252
|
const ws = MockWebSocket.instances[0];
|
|
2232
2253
|
await agent.ask("cancel me");
|
|
2233
2254
|
|
|
2234
|
-
|
|
2255
|
+
const interruptResult = agent.interrupt();
|
|
2256
|
+
assert.equal(interruptResult, 1);
|
|
2235
2257
|
assert.deepEqual(agent.interrupts, ["speak"]);
|
|
2236
2258
|
assert.deepEqual(JSON.parse(ws.sent.at(-1)), { type: "interrupt" });
|
|
2259
|
+
|
|
2260
|
+
const sentCount = ws.sent.length;
|
|
2261
|
+
await agent.interruptConversation("user");
|
|
2262
|
+
assert.equal(ws.sent.length, sentCount + 1);
|
|
2263
|
+
assert.deepEqual(agent.interrupts, ["speak", "speak"]);
|
|
2264
|
+
assert.deepEqual(JSON.parse(ws.sent.at(-1)), { type: "interrupt" });
|
|
2237
2265
|
});
|
|
2238
2266
|
|
|
2239
2267
|
test("XingyunAvatarAgent keeps ASR listening when interrupting a conversation", async () => {
|
|
@@ -2248,7 +2276,7 @@ test("XingyunAvatarAgent keeps ASR listening when interrupting a conversation",
|
|
|
2248
2276
|
const recorder = browser.recorders[0];
|
|
2249
2277
|
ws.sent = [];
|
|
2250
2278
|
|
|
2251
|
-
|
|
2279
|
+
agent.interrupt();
|
|
2252
2280
|
const audioAfterInterrupt = new Blob(["audio-after-interrupt"], {
|
|
2253
2281
|
type: "audio/webm;codecs=opus",
|
|
2254
2282
|
});
|
package/src/agent/index.ts
CHANGED
|
@@ -262,7 +262,9 @@ export default class XingyunAvatarAgent extends XmovAvatar {
|
|
|
262
262
|
return this.agentState;
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
|
|
265
|
+
/**
|
|
266
|
+
* @deprecated Use getAgentState() instead.
|
|
267
|
+
*/
|
|
266
268
|
getState() {
|
|
267
269
|
return this.getAgentState();
|
|
268
270
|
}
|
|
@@ -460,13 +462,6 @@ export default class XingyunAvatarAgent extends XmovAvatar {
|
|
|
460
462
|
}
|
|
461
463
|
}
|
|
462
464
|
|
|
463
|
-
override speak(...args: Parameters<XmovAvatar["speak"]>) {
|
|
464
|
-
if (args[2] ?? true) {
|
|
465
|
-
this.suppressNextVoiceEnd = true;
|
|
466
|
-
}
|
|
467
|
-
return super.speak(...args);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
465
|
override async destroy(reason = "user") {
|
|
471
466
|
if (this.agentDestroyed) {
|
|
472
467
|
if (this.agentState !== "destroyed") {
|
|
@@ -510,7 +505,16 @@ export default class XingyunAvatarAgent extends XmovAvatar {
|
|
|
510
505
|
});
|
|
511
506
|
}
|
|
512
507
|
|
|
513
|
-
|
|
508
|
+
override speak(text: string): string | undefined {
|
|
509
|
+
// XmovAvatar Replay dispatches recorded TTSA events through this.speak(...).
|
|
510
|
+
// Preserve that inherited multi-argument call path without exposing it as
|
|
511
|
+
// the Agent's public E2E API.
|
|
512
|
+
if (arguments.length > 1) {
|
|
513
|
+
if (arguments[2] ?? true) {
|
|
514
|
+
this.suppressNextVoiceEnd = true;
|
|
515
|
+
}
|
|
516
|
+
return super.speak(text, arguments[1], arguments[2], arguments[3]);
|
|
517
|
+
}
|
|
514
518
|
this.assertRunning();
|
|
515
519
|
this.suppressNextVoiceEnd = false;
|
|
516
520
|
this.emitConversation({ state: "speaking-directly", text });
|
|
@@ -522,6 +526,14 @@ export default class XingyunAvatarAgent extends XmovAvatar {
|
|
|
522
526
|
is_end: true,
|
|
523
527
|
},
|
|
524
528
|
});
|
|
529
|
+
return undefined;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* @deprecated Use speak(text) instead.
|
|
534
|
+
*/
|
|
535
|
+
async speakByE2E(text: string): Promise<void> {
|
|
536
|
+
this.speak(text);
|
|
525
537
|
}
|
|
526
538
|
|
|
527
539
|
async startASR(options: AgentASRStartOptions = {}): Promise<void> {
|
|
@@ -670,18 +682,25 @@ export default class XingyunAvatarAgent extends XmovAvatar {
|
|
|
670
682
|
}
|
|
671
683
|
}
|
|
672
684
|
|
|
673
|
-
|
|
685
|
+
override interrupt(): number {
|
|
674
686
|
if (this.agentDestroyed) {
|
|
675
|
-
return;
|
|
687
|
+
return 0;
|
|
676
688
|
}
|
|
677
689
|
|
|
678
|
-
void reason;
|
|
679
|
-
|
|
680
690
|
this.sendControl({
|
|
681
691
|
type: "interrupt",
|
|
682
692
|
});
|
|
683
|
-
super.interrupt("speak");
|
|
693
|
+
const interruptedSpeechId = super.interrupt("speak");
|
|
684
694
|
this.emitConversation({ state: "interrupted" });
|
|
695
|
+
return interruptedSpeechId;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* @deprecated Use interrupt() instead. The reason parameter is ignored.
|
|
700
|
+
*/
|
|
701
|
+
async interruptConversation(reason = "user"): Promise<void> {
|
|
702
|
+
void reason;
|
|
703
|
+
this.interrupt();
|
|
685
704
|
}
|
|
686
705
|
|
|
687
706
|
protected override async onSessionReloaded(sessionInfo: ISessionResponse): Promise<void> {
|
|
@@ -717,10 +736,6 @@ export default class XingyunAvatarAgent extends XmovAvatar {
|
|
|
717
736
|
}
|
|
718
737
|
}
|
|
719
738
|
|
|
720
|
-
override reloadSuccess() {
|
|
721
|
-
super.reloadSuccess();
|
|
722
|
-
}
|
|
723
|
-
|
|
724
739
|
private async replaceE2EConnection(sessionInfo?: AgentSessionInfo) {
|
|
725
740
|
const { wsUrl, token } = this.resolveE2EConnection(sessionInfo);
|
|
726
741
|
const previousClient = this.e2eClient;
|