@firetable/project-xiaochun 0.1.14 → 0.1.15
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/README-CN.md +105 -17
- package/README.md +105 -17
- package/dist/avatar-element.d.ts +23 -6
- package/dist/chunks/{chunk-DUU5PTKT.js → chunk-KKRZ3SUD.js} +50 -2
- package/dist/chunks/chunk-KKRZ3SUD.js.map +7 -0
- package/dist/chunks/chunk-KQSKHPVF.js +957 -0
- package/dist/chunks/chunk-KQSKHPVF.js.map +7 -0
- package/dist/chunks/{chunk-HLVD6HQM.js → chunk-T6EHFEEZ.js} +81 -9
- package/dist/chunks/chunk-T6EHFEEZ.js.map +7 -0
- package/dist/client.d.ts +127 -6
- package/dist/element.cjs +538 -74
- package/dist/element.cjs.map +4 -4
- package/dist/element.js +4 -4
- package/dist/gesture-box.d.ts +72 -0
- package/dist/index.cjs +565 -74
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +22 -4
- package/dist/index.js.map +1 -1
- package/dist/loader.global.js +2 -2
- package/dist/loader.global.js.map +4 -4
- package/dist/protocol.cjs +49 -1
- package/dist/protocol.cjs.map +2 -2
- package/dist/protocol.d.ts +178 -9
- package/dist/protocol.js +20 -2
- package/dist/react.cjs +507 -80
- package/dist/react.cjs.map +4 -4
- package/dist/react.d.ts +17 -1
- package/dist/react.js +49 -15
- package/dist/react.js.map +2 -2
- package/package.json +5 -3
- package/dist/chunks/chunk-DUU5PTKT.js.map +0 -7
- package/dist/chunks/chunk-HLVD6HQM.js.map +0 -7
- package/dist/chunks/chunk-OLVSOJML.js +0 -579
- package/dist/chunks/chunk-OLVSOJML.js.map +0 -7
package/dist/protocol.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @firetable/project-xiaochun v0.1.
|
|
1
|
+
/*! @firetable/project-xiaochun v0.1.15 | MIT | https://github.com/FireTable/project-xiaochun */
|
|
2
2
|
"use strict";
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -25,12 +25,21 @@ __export(protocol_exports, {
|
|
|
25
25
|
XC_DEFAULT_SRC: () => XC_DEFAULT_SRC,
|
|
26
26
|
XC_FRAME_TO_HOST: () => XC_FRAME_TO_HOST,
|
|
27
27
|
XC_HOST_TO_FRAME: () => XC_HOST_TO_FRAME,
|
|
28
|
+
XC_ID_RE: () => XC_ID_RE,
|
|
28
29
|
XC_IMPLEMENTED_COMMANDS: () => XC_IMPLEMENTED_COMMANDS,
|
|
29
30
|
XC_PROTOCOL_MAPPING: () => XC_PROTOCOL_MAPPING,
|
|
30
31
|
XC_PROTOCOL_VERSION: () => XC_PROTOCOL_VERSION,
|
|
32
|
+
XC_UI_LEGACY_PARTS: () => XC_UI_LEGACY_PARTS,
|
|
33
|
+
XC_UI_PARTS: () => XC_UI_PARTS,
|
|
31
34
|
XC_UNSUPPORTED_COMMANDS: () => XC_UNSUPPORTED_COMMANDS,
|
|
35
|
+
XC_WINDOW_CORNER_RADIUS: () => XC_WINDOW_CORNER_RADIUS,
|
|
32
36
|
isXcEnvelope: () => isXcEnvelope,
|
|
37
|
+
isXcId: () => isXcId,
|
|
33
38
|
normalizeOrigin: () => normalizeOrigin,
|
|
39
|
+
normalizeXcUiOption: () => normalizeXcUiOption,
|
|
40
|
+
parseXcUiList: () => parseXcUiList,
|
|
41
|
+
parseXcUiParam: () => parseXcUiParam,
|
|
42
|
+
xcHasOwn: () => xcHasOwn,
|
|
34
43
|
xcMessage: () => xcMessage
|
|
35
44
|
});
|
|
36
45
|
module.exports = __toCommonJS(protocol_exports);
|
|
@@ -48,6 +57,9 @@ var XC_HOST_TO_FRAME = [
|
|
|
48
57
|
"xc.lookAt",
|
|
49
58
|
"xc.pointer",
|
|
50
59
|
"xc.setModel",
|
|
60
|
+
"xc.setOutfit",
|
|
61
|
+
"xc.setScene",
|
|
62
|
+
"xc.prefetch",
|
|
51
63
|
"xc.setConfig",
|
|
52
64
|
"xc.mic",
|
|
53
65
|
"xc.pause",
|
|
@@ -58,10 +70,15 @@ var XC_FRAME_TO_HOST = [
|
|
|
58
70
|
"xc.ready",
|
|
59
71
|
"xc.load.progress",
|
|
60
72
|
"xc.loaded",
|
|
73
|
+
"xc.outfit-changed",
|
|
74
|
+
"xc.scene-changed",
|
|
75
|
+
"xc.prefetched",
|
|
61
76
|
"xc.state",
|
|
62
77
|
"xc.stt",
|
|
63
78
|
"xc.utterance",
|
|
64
79
|
"xc.hit-region",
|
|
80
|
+
"xc.gesture-move",
|
|
81
|
+
"xc.gesture-resize",
|
|
65
82
|
"xc.error"
|
|
66
83
|
];
|
|
67
84
|
var XC_IMPLEMENTED_COMMANDS = [
|
|
@@ -73,6 +90,9 @@ var XC_IMPLEMENTED_COMMANDS = [
|
|
|
73
90
|
"xc.expression",
|
|
74
91
|
"xc.pointer",
|
|
75
92
|
"xc.setModel",
|
|
93
|
+
"xc.setOutfit",
|
|
94
|
+
"xc.setScene",
|
|
95
|
+
"xc.prefetch",
|
|
76
96
|
"xc.setConfig",
|
|
77
97
|
"xc.mic",
|
|
78
98
|
"xc.pause",
|
|
@@ -87,6 +107,34 @@ var XC_PROTOCOL_MAPPING = [
|
|
|
87
107
|
{ xc: "xc.audio", note: "source \u4E3A ArrayBuffer / Blob", action: "audio", url: null },
|
|
88
108
|
{ xc: "xc.audio.chunk / xc.audio.end", note: "\u6D41\u5F0F PCM", action: "audio", url: null }
|
|
89
109
|
];
|
|
110
|
+
var XC_WINDOW_CORNER_RADIUS = 20;
|
|
111
|
+
var XC_UI_PARTS = ["chat", "bubble", "outfit", "scene"];
|
|
112
|
+
var XC_UI_PART_SET = new Set(XC_UI_PARTS);
|
|
113
|
+
var XC_UI_LEGACY_PARTS = ["chat", "bubble"];
|
|
114
|
+
function parseXcUiList(items) {
|
|
115
|
+
const seen = /* @__PURE__ */ new Set();
|
|
116
|
+
const unknown = [];
|
|
117
|
+
for (const it of items) {
|
|
118
|
+
const name = typeof it === "string" ? it.trim().toLowerCase() : "";
|
|
119
|
+
if (XC_UI_PART_SET.has(name)) seen.add(name);
|
|
120
|
+
else unknown.push(typeof it === "string" ? it.slice(0, 40) : String(typeof it));
|
|
121
|
+
}
|
|
122
|
+
return { parts: XC_UI_PARTS.filter((p) => seen.has(p)), unknown, legacy: false };
|
|
123
|
+
}
|
|
124
|
+
function parseXcUiParam(raw) {
|
|
125
|
+
const v = (raw ?? "").trim().toLowerCase();
|
|
126
|
+
if (v === "" || v === "0" || v === "false") return { parts: [], unknown: [], legacy: false };
|
|
127
|
+
if (v === "1" || v === "true") return { parts: [...XC_UI_LEGACY_PARTS], unknown: [], legacy: true };
|
|
128
|
+
return parseXcUiList(v.split(",").filter((x) => x.trim() !== ""));
|
|
129
|
+
}
|
|
130
|
+
function normalizeXcUiOption(v) {
|
|
131
|
+
if (v === true) return { parts: [...XC_UI_LEGACY_PARTS], unknown: [], legacy: true };
|
|
132
|
+
if (Array.isArray(v)) return parseXcUiList(v);
|
|
133
|
+
return { parts: [], unknown: [], legacy: false };
|
|
134
|
+
}
|
|
135
|
+
var XC_ID_RE = /^[a-z][a-z0-9_]{0,63}$/;
|
|
136
|
+
var isXcId = (v) => typeof v === "string" && XC_ID_RE.test(v);
|
|
137
|
+
var xcHasOwn = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
|
|
90
138
|
function xcMessage(type, payload, id) {
|
|
91
139
|
const m = { type, v: XC_PROTOCOL_VERSION, payload };
|
|
92
140
|
if (id !== void 0) m.id = id;
|
package/dist/protocol.cjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/protocol.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * protocol.ts \u2014 Project XiaoChun `<iframe>` \u5D4C\u5165\u534F\u8BAE (\u5E38\u91CF + \u7C7B\u578B, \u96F6\u8FD0\u884C\u65F6\u4F9D\u8D56)\u3002\n *\n * \u5355\u4E00\u4E8B\u5B9E\u6E90: SDK (\u672C\u5305) \u4E0E `/embed` \u9875\u9762 (\u4E3B\u4ED3\u5E93 src/embed/) \u5171\u7528\u8FD9\u4E00\u4EFD\u3002\n * \u6240\u6709\u6D88\u606F\u540D\u7EDF\u4E00 `xc.` \u524D\u7F00; \u4FE1\u5C01 `{ type, v, id?, payload? }`\u3002\n *\n * \u901A\u9053:\n * 1. \u63E1\u624B: iframe \u2192 \u5BBF\u4E3B `xc.ready` (window.postMessage, targetOrigin = \u5BBF\u4E3B origin)\n * \u5BBF\u4E3B \u2192 iframe `xc.init` (window.postMessage, targetOrigin = iframe origin, \u8F6C\u79FB MessagePort)\n * 2. \u63E1\u624B\u540E\u5168\u90E8\u8D70 MessageChannel \u7AEF\u53E3 (\u7AEF\u53E3\u672C\u8EAB\u5373\u51ED\u8BC1, \u4E0D\u518D\u4F9D\u8D56 `*`)\n */\n\n/** \u534F\u8BAE\u7248\u672C; \u4E0D\u517C\u5BB9\u53D8\u66F4\u65F6 +1\u3002 */\nexport const XC_PROTOCOL_VERSION = 1 as const;\n\n/** \u5B98\u65B9\u90E8\u7F72\u7684 origin / embed \u5165\u53E3\u3002 */\nexport const XC_DEFAULT_ORIGIN = 'https://xiaochun.firetable.tech';\nexport const XC_DEFAULT_SRC = `${XC_DEFAULT_ORIGIN}/embed`;\n\n/** \u5BBF\u4E3B \u2192 iframe \u547D\u4EE4\u3002 */\nexport const XC_HOST_TO_FRAME = [\n 'xc.init',\n 'xc.say',\n 'xc.audio',\n 'xc.audio.chunk',\n 'xc.audio.end',\n 'xc.motion',\n 'xc.expression',\n 'xc.lookAt',\n 'xc.pointer',\n 'xc.setModel',\n 'xc.setConfig',\n 'xc.mic',\n 'xc.pause',\n 'xc.resume',\n 'xc.destroy',\n] as const;\n\n/** iframe \u2192 \u5BBF\u4E3B \u4E8B\u4EF6\u3002 */\nexport const XC_FRAME_TO_HOST = [\n 'xc.ready',\n 'xc.load.progress',\n 'xc.loaded',\n 'xc.state',\n 'xc.stt',\n 'xc.utterance',\n 'xc.hit-region',\n 'xc.error',\n] as const;\n\nexport type XcHostMessageType = (typeof XC_HOST_TO_FRAME)[number];\nexport type XcFrameMessageType = (typeof XC_FRAME_TO_HOST)[number];\n\n/** \u5F53\u524D /embed \u5DF2\u5B9E\u73B0\u7684\u547D\u4EE4 (xc.init \u662F\u63E1\u624B, \u4E0D\u8BA1\u5165)\u3002 */\nexport const XC_IMPLEMENTED_COMMANDS = [\n 'xc.say',\n 'xc.audio',\n 'xc.audio.chunk',\n 'xc.audio.end',\n 'xc.motion',\n 'xc.expression',\n 'xc.pointer',\n 'xc.setModel',\n 'xc.setConfig',\n 'xc.mic',\n 'xc.pause',\n 'xc.resume',\n 'xc.destroy',\n] as const satisfies readonly XcHostMessageType[];\n\n/**\n * \u6682\u4E0D\u652F\u6301 (\u5E95\u5C42\u65E0\u5BF9\u5E94\u80FD\u529B), /embed \u56DE `xc.error{code:'unsupported'}`\u3002\n * TODO(xc.lookAt): \u89C6\u7EBF\u76EE\u524D\u7531\u76F8\u673A + \u968F\u673A\u626B\u89C6\u9A71\u52A8 (GazeController), \u6CA1\u6709\"\u5916\u90E8\u6307\u5B9A\u6CE8\u89C6\u70B9\"\u7684\u5165\u53E3;\n * \u9700\u8981\u5148\u5728 gaze \u5C42\u52A0 overrideTarget \u518D\u5F00\u653E\u3002\n */\nexport const XC_UNSUPPORTED_COMMANDS = ['xc.lookAt'] as const satisfies readonly XcHostMessageType[];\n\n/**\n * xc.* \u2194 xiaochun:// \u5BF9\u7167\u8868 (\u540C\u4E00\u8BED\u4E49\u7684\u4E24\u4E2A\u4F20\u8F93\u5C42, \u4E3B\u5E94\u7528\u5185\u8D70\u540C\u4E00\u4E2A handler: src/core/protocol/handler.ts)\u3002\n * - `action`: \u5185\u90E8 protocol action \u5BF9\u8C61 (src/core/protocol/types.ts); null = \u6CA1\u6709 deep link \u5BF9\u5E94\u7269\u3002\n * - `url`: \u5BF9\u5E94\u7684 deep link \u5F62\u5F0F (OS \u7EA7, \u53EA\u5728 Tauri \u58F3\u751F\u6548; iframe \u4E0D\u4F1A\u54CD\u5E94 xiaochun://)\u3002\n */\nexport const XC_PROTOCOL_MAPPING = [\n { xc: 'xc.say', note: 'mode:\"speak\" (\u9ED8\u8BA4)', action: 'speak', url: 'xiaochun://speak?text=\u2026' },\n { xc: 'xc.say', note: 'mode:\"chat\" (\u8D70 LLM)', action: null, url: null },\n { xc: 'xc.audio', note: 'source \u4E3A URL \u5B57\u7B26\u4E32', action: 'speak', url: 'xiaochun://speak?audioUrl=\u2026[&text=\u2026]' },\n { xc: 'xc.audio', note: 'source \u4E3A ArrayBuffer / Blob', action: 'audio', url: null },\n { xc: 'xc.audio.chunk / xc.audio.end', note: '\u6D41\u5F0F PCM', action: 'audio', url: null },\n] as const;\n\nexport type XcErrorCode =\n | 'unsupported' // \u547D\u4EE4/\u53C2\u6570\u5B58\u5728\u4F46\u5E95\u5C42\u6682\u65E0\u80FD\u529B\n | 'bad_request' // \u53C2\u6570\u7F3A\u5931/\u975E\u6CD5\n | 'not_ready' // \u6A21\u578B\u5C1A\u672A\u52A0\u8F7D\u5B8C\u6210\n | 'origin_denied' // \u63E1\u624B\u6765\u6E90\u4E0D\u5728\u767D\u540D\u5355\n | 'failed'; // \u6267\u884C\u671F\u5F02\u5E38\n\nexport type XcLang = 'zh-CN' | 'en' | 'ja';\nexport type XcPhase = 'idle' | 'loading' | 'thinking' | 'speaking' | 'listening' | 'paused';\n/** lazy = \u4E0D\u9884\u70ED WebLLM / EMAGE, \u9996\u6B21\u4E92\u52A8\u518D\u52A0\u8F7D (\u9ED8\u8BA4); eager = \u7ACB\u5373\u9884\u70ED\u3002 */\nexport type XcHeavyMode = 'lazy' | 'eager';\n\nexport interface XcEnvelope<T extends string = string, P = unknown> {\n type: T;\n v: typeof XC_PROTOCOL_VERSION;\n /** \u5BBF\u4E3B\u547D\u4EE4\u53EF\u5E26 id; \u5BF9\u5E94\u7684 xc.error / xc.utterance \u4F1A\u56DE\u5E26\u540C\u4E00\u4E2A id\u3002 */\n id?: string;\n payload: P;\n}\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u5BBF\u4E3B \u2192 iframe payload \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nexport interface XcConfig {\n lang?: XcLang;\n /** \u80CC\u666F\u900F\u660E (\u53E0\u5728\u5BBF\u4E3B\u9875\u9762\u4E0A)\u3002 */\n transparent?: boolean;\n /** \u662F\u5426\u663E\u793A iframe \u5185\u7F6E UI (ChatBar)\u3002\u9ED8\u8BA4 false\u3002 */\n ui?: boolean;\n /** \u91CD\u8D44\u6E90\u52A0\u8F7D\u7B56\u7565, \u89C1 XcHeavyMode\u3002 */\n heavy?: XcHeavyMode;\n}\n\nexport interface XcInitPayload {\n /** \u5BBF\u4E3B\u58F0\u660E\u7684\u81EA\u8EAB origin (iframe \u4F1A\u4E0E event.origin \u4EA4\u53C9\u6821\u9A8C)\u3002 */\n hostOrigin: string;\n config?: XcConfig;\n}\n\nexport interface XcSayPayload {\n text: string;\n /**\n * 'speak' (\u9ED8\u8BA4): \u76F4\u63A5 TTS + \u52A8\u4F5C, \u4E0D\u7ECF\u8FC7 LLM\u3002\n * 'chat': \u5F53\u4F5C\u7528\u6237\u8F93\u5165\u8D70 WebLLM / \u81EA\u5B9A\u4E49 provider (\u4F1A\u89E6\u53D1\u5927\u6A21\u578B\u52A0\u8F7D)\u3002\n */\n mode?: 'speak' | 'chat';\n}\n\n/** \u5BBF\u4E3B\u97F3\u9891\u7684\u7F16\u7801: encoded = \u5BB9\u5668\u683C\u5F0F (mp3/wav/ogg/aac/webm\u2026 \u7531\u6D4F\u89C8\u5668 decodeAudioData \u51B3\u5B9A); pcm16 / float32 = \u65E0\u5934\u539F\u59CB PCM (\u5C0F\u7AEF, \u4EA4\u9519)\u3002 */\nexport type XcAudioFormat = 'encoded' | 'pcm16' | 'float32';\n\nexport interface XcAudioOptions {\n /** \u6C14\u6CE1\u91CC\u663E\u793A\u7684\u6587\u5B57 (\u53EF\u9009; \u4E0D\u5F71\u54CD\u97F3\u9891\u4E0E\u52A8\u4F5C)\u3002 */\n text?: string;\n /** \u662F\u5426\u7531 EMAGE \u6839\u636E\u8FD9\u6BB5\u97F3\u9891\u751F\u6210\u5168\u8EAB\u52A8\u4F5C, \u9ED8\u8BA4 true\u3002false = \u53EA\u64AD\u653E\u97F3\u9891 (\u6B64\u65F6\u5B8C\u5168\u4E0D\u52A0\u8F7D EMAGE)\u3002 */\n motion?: boolean;\n /** \u662F\u5426\u9A71\u52A8\u53E3\u578B (\u97F3\u91CF RMS \u2192 'aa'), \u9ED8\u8BA4 true\u3002 */\n lipsync?: boolean;\n}\n\n/**\n * xc.audio \u2014 \u6574\u6BB5\u97F3\u9891: iframe \u5185\u89E3\u7801 \u2192 16 kHz \u2192 EMAGE \u7A97\u53E3\u63A8\u7406 + \u540C\u4E00\u6BB5\u97F3\u9891\u64AD\u653E + \u53E3\u578B, \u64AD\u5B8C\u56DE xc.utterance{phase:'end'}\u3002\n * \u4FE1\u5C01 `id` \u7528\u4E8E\u5173\u8054 xc.utterance / xc.error\u3002\u5927 ArrayBuffer \u8BF7\u653E\u8FDB postMessage \u7684 transfer \u5217\u8868 (SDK \u9ED8\u8BA4\u8FD9\u4E48\u505A)\u3002\n */\nexport interface XcAudioPayload extends XcAudioOptions {\n /** ArrayBuffer / Blob (structured clone) \u6216 URL \u5B57\u7B26\u4E32 (iframe \u5185 fetch, \u4EC5 https \u6216\u540C\u6E90, \u9700 CORS)\u3002 */\n source: ArrayBuffer | Blob | string;\n /** MIME \u63D0\u793A, \u5982 'audio/mpeg'\u3002\u4EC5\u4F5C\u4FE1\u606F, \u89E3\u7801\u4EE5\u6D4F\u89C8\u5668\u55C5\u63A2\u4E3A\u51C6\u3002 */\n mimeType?: string;\n /** \u9ED8\u8BA4 'encoded'\u3002 */\n format?: XcAudioFormat;\n /** \u4EC5\u539F\u59CB PCM \u9700\u8981 (\u9ED8\u8BA4 16000); \u5BB9\u5668\u683C\u5F0F\u5FFD\u7565 (\u4EE5\u6587\u4EF6\u5185\u7684\u91C7\u6837\u7387\u4E3A\u51C6)\u3002\u8303\u56F4 8000~96000\u3002 */\n sampleRate?: number;\n /** \u539F\u59CB PCM \u7684\u58F0\u9053\u6570 (\u4EA4\u9519), 1 \u6216 2, \u9ED8\u8BA4 1; \u5185\u90E8\u4E0B\u6DF7\u4E3A\u5355\u58F0\u9053\u3002 */\n channels?: 1 | 2;\n}\n\n/**\n * xc.audio.chunk \u2014 \u6D41\u5F0F\u97F3\u9891\u5206\u5757 (\u539F\u59CB PCM)\u3002\u4FE1\u5C01 `id` = \u6D41 id: \u540C\u4E00\u4E2A id \u7684\u7B2C\u4E00\u4E2A chunk \u5F00\u542F\u4E00\u6B21\u8BF4\u8BDD (\u56DE xc.utterance start),\n * \u4E4B\u540E\u7684 chunk \u8FFD\u52A0; \u4EE5 xc.audio.end \u6536\u5C3E\u3002\u6BCF\u4E2A chunk \u7684 `data` \u5E94\u653E\u8FDB transfer \u5217\u8868\u3002\n * \u9009\u9879 (text/motion/lipsync) \u53EA\u5728\u7B2C\u4E00\u4E2A chunk \u91CC\u751F\u6548; sampleRate/format \u6574\u6761\u6D41\u5FC5\u987B\u4E00\u81F4\u3002\n */\nexport interface XcAudioChunkPayload extends XcAudioOptions {\n data: ArrayBuffer;\n format: Exclude<XcAudioFormat, 'encoded'>;\n /** 8000~96000\u3002 */\n sampleRate: number;\n channels?: 1 | 2;\n}\n\nexport interface XcAudioEndPayload {\n /** true = \u7ACB\u5373\u6253\u65AD (\u4E22\u5F03\u672A\u64AD\u653E\u7684\u97F3\u9891); \u9ED8\u8BA4 false = \u64AD\u5B8C\u5DF2\u6536\u5230\u7684\u97F3\u9891\u540E\u7ED3\u675F\u3002 */\n abort?: boolean;\n}\n\nexport interface XcMotionPayload {\n /** .vrma URL (https:// \u6216\u540C\u6E90\u8DEF\u5F84)\u3002\u4E0E stop \u4E8C\u9009\u4E00\u3002 */\n url?: string;\n /** \u5185\u7F6E\u52A8\u4F5C\u540D, \u76EE\u524D\u4EC5 'thinking'\u3002 */\n name?: 'thinking';\n stop?: boolean;\n loop?: boolean;\n /** \u6DE1\u5165\u6DE1\u51FA\u79D2\u6570, \u8303\u56F4 0.26~3, \u8C03\u5927\u66F4\u67D4\u548C\u4F46\u54CD\u5E94\u66F4\u6162\u3002 */\n fadeDuration?: number;\n /** \u64AD\u653E\u500D\u901F, \u8303\u56F4 0.25~3\u3002 */\n timeScale?: number;\n mask?: 'all' | 'upperBody';\n}\n\nexport interface XcExpressionPayload {\n name: 'neutral' | 'happy' | 'angry' | 'sad' | 'relaxed' | 'surprised';\n}\n\n/** xc.lookAt \u2014 TODO: \u6682\u4E0D\u652F\u6301, \u6536\u5230\u4F1A\u8FD4\u56DE unsupported\u3002 */\nexport interface XcLookAtPayload {\n /** \u5F52\u4E00\u5316 (-1..1), \u76F8\u5BF9 iframe \u4E2D\u5FC3\u3002 */\n x: number;\n y: number;\n}\n\nexport interface XcPointerPayload {\n /** iframe \u5185 client \u5750\u6807 (px)\u3002SDK \u4F1A\u628A\u5BBF\u4E3B pointer \u4E8B\u4EF6\u6362\u7B97\u597D\u518D\u53D1\u3002 */\n x: number;\n y: number;\n}\n\nexport interface XcSetModelPayload {\n /** \u5B8C\u6574 .vrm / .vrmaddon / .vrmbase URL (\u9700 CORS)\u3002 */\n url?: string;\n /** \u5185\u7F6E\u670D\u88C5 key, \u89C1\u4E3B\u4ED3\u5E93 APP_CONFIG.model.addons, \u5982 'xiaochun_maid'\u3002 */\n outfit?: string;\n name?: string;\n}\n\nexport interface XcMicPayload {\n enabled: boolean;\n}\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 iframe \u2192 \u5BBF\u4E3B payload \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nexport interface XcReadyPayload {\n version: string;\n protocol: typeof XC_PROTOCOL_VERSION;\n capabilities: {\n commands: string[];\n unsupported: string[];\n stt: boolean;\n transparent: boolean;\n /** \u5BBF\u4E3B\u97F3\u9891\u80FD\u529B (xc.audio / xc.audio.chunk)\u3002\u65E7\u7248 /embed \u6CA1\u6709\u8FD9\u4E2A\u5B57\u6BB5\u3002 */\n audio?: { formats: XcAudioFormat[]; streaming: boolean; maxSeconds: number };\n /** iframe \u5185 `self.crossOriginIsolated` (true = \u53EF\u7528 SharedArrayBuffer / \u591A\u7EBF\u7A0B wasm)\u3002\u65E7\u7248 /embed \u6CA1\u6709\u8FD9\u4E2A\u5B57\u6BB5\u3002\u4EC5\u8BCA\u65AD\u7528\u3002 */\n crossOriginIsolated?: boolean;\n };\n}\n\nexport interface XcLoadProgressPayload {\n phase: 'model';\n /** 0~100 */\n progress: number;\n}\n\nexport interface XcLoadedPayload {\n model: string;\n}\n\nexport interface XcStatePayload {\n phase: XcPhase;\n paused: boolean;\n heavy: XcHeavyMode;\n}\n\nexport type XcSttPayload =\n | { kind: 'state'; state: 'idle' | 'loading' | 'listening' | 'recognizing' | 'error' }\n | { kind: 'progress'; percent: number }\n | { kind: 'text'; text: string };\n\nexport interface XcUtterancePayload {\n phase: 'start' | 'end';\n text: string;\n /** 'text' = xc.say (TTS); 'audio' = xc.audio / xc.audio.chunk (\u5BBF\u4E3B\u97F3\u9891, \u6CA1\u6709\u8D70 TTS)\u3002\u65E7\u7248 /embed \u6CA1\u6709\u8FD9\u4E2A\u5B57\u6BB5\u3002 */\n kind?: 'text' | 'audio';\n}\n\nexport interface XcHitRegionPayload {\n hit: boolean;\n x: number;\n y: number;\n}\n\nexport interface XcErrorPayload {\n code: XcErrorCode;\n message: string;\n /** \u89E6\u53D1\u9519\u8BEF\u7684\u547D\u4EE4\u540D (\u5982 'xc.lookAt')\u3002 */\n command?: string;\n}\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u7C7B\u578B\u6620\u5C04 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nexport interface XcHostPayloadMap {\n 'xc.init': XcInitPayload;\n 'xc.say': XcSayPayload;\n 'xc.audio': XcAudioPayload;\n 'xc.audio.chunk': XcAudioChunkPayload;\n 'xc.audio.end': XcAudioEndPayload;\n 'xc.motion': XcMotionPayload;\n 'xc.expression': XcExpressionPayload;\n 'xc.lookAt': XcLookAtPayload;\n 'xc.pointer': XcPointerPayload;\n 'xc.setModel': XcSetModelPayload;\n 'xc.setConfig': XcConfig;\n 'xc.mic': XcMicPayload;\n 'xc.pause': undefined;\n 'xc.resume': undefined;\n 'xc.destroy': undefined;\n}\n\nexport interface XcFramePayloadMap {\n 'xc.ready': XcReadyPayload;\n 'xc.load.progress': XcLoadProgressPayload;\n 'xc.loaded': XcLoadedPayload;\n 'xc.state': XcStatePayload;\n 'xc.stt': XcSttPayload;\n 'xc.utterance': XcUtterancePayload;\n 'xc.hit-region': XcHitRegionPayload;\n 'xc.error': XcErrorPayload;\n}\n\nexport type XcHostMessage = {\n [K in XcHostMessageType]: XcEnvelope<K, XcHostPayloadMap[K]>;\n}[XcHostMessageType];\n\nexport type XcFrameMessage = {\n [K in XcFrameMessageType]: XcEnvelope<K, XcFramePayloadMap[K]>;\n}[XcFrameMessageType];\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u5C0F\u5DE5\u5177 (SDK \u4E0E /embed \u5171\u7528) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nexport function xcMessage<K extends XcHostMessageType | XcFrameMessageType>(\n type: K,\n payload?: unknown,\n id?: string,\n): XcEnvelope<K, any> {\n const m: XcEnvelope<K, any> = { type, v: XC_PROTOCOL_VERSION, payload };\n if (id !== undefined) m.id = id;\n return m;\n}\n\n/** \u662F\u5426\u4E3A\u5408\u6CD5 xc \u4FE1\u5C01 (\u53EA\u68C0\u67E5\u7ED3\u6784, \u4E0D\u68C0\u67E5 payload)\u3002 */\nexport function isXcEnvelope(data: unknown): data is XcEnvelope {\n if (!data || typeof data !== 'object') return false;\n const d = data as Record<string, unknown>;\n return typeof d.type === 'string' && d.type.startsWith('xc.') && d.v === XC_PROTOCOL_VERSION;\n}\n\n/**\n * \u628A\u7528\u6237\u7ED9\u7684 origin \u89C4\u6574\u6210\u4E25\u683C\u7684 `scheme://host[:port]`\u3002\n * \u62D2\u7EDD '*' / \u901A\u914D / \u975E http(s) / \u5E26\u8DEF\u5F84\u7684\u5B57\u7B26\u4E32 \u2192 \u8FD4\u56DE null\u3002\n */\nexport function normalizeOrigin(input: string | null | undefined): string | null {\n if (!input || input === '*' || input === 'null') return null;\n try {\n const u = new URL(input);\n if (u.protocol !== 'https:' && u.protocol !== 'http:') return null;\n return u.origin;\n } catch {\n return null;\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaO,IAAM,sBAAsB;AAG5B,IAAM,oBAAoB;AAC1B,IAAM,iBAAiB,GAAG,iBAAiB;AAG3C,IAAM,mBAAmB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,mBAAmB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAMO,IAAM,0BAA0B;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,IAAM,0BAA0B,CAAC,WAAW;AAO5C,IAAM,sBAAsB;AAAA,EACjC,EAAE,IAAI,UAAU,MAAM,+BAAqB,QAAQ,SAAS,KAAK,+BAA0B;AAAA,EAC3F,EAAE,IAAI,UAAU,MAAM,4BAAuB,QAAQ,MAAM,KAAK,KAAK;AAAA,EACrE,EAAE,IAAI,YAAY,MAAM,wCAAoB,QAAQ,SAAS,KAAK,iDAAuC;AAAA,EACzG,EAAE,IAAI,YAAY,MAAM,oCAA+B,QAAQ,SAAS,KAAK,KAAK;AAAA,EAClF,EAAE,IAAI,iCAAiC,MAAM,oBAAU,QAAQ,SAAS,KAAK,KAAK;AACpF;
|
|
4
|
+
"sourcesContent": ["/**\n * protocol.ts \u2014 Project XiaoChun `<iframe>` \u5D4C\u5165\u534F\u8BAE (\u5E38\u91CF + \u7C7B\u578B, \u96F6\u8FD0\u884C\u65F6\u4F9D\u8D56)\u3002\n *\n * \u5355\u4E00\u4E8B\u5B9E\u6E90: SDK (\u672C\u5305) \u4E0E `/embed` \u9875\u9762 (\u4E3B\u4ED3\u5E93 src/embed/) \u5171\u7528\u8FD9\u4E00\u4EFD\u3002\n * \u6240\u6709\u6D88\u606F\u540D\u7EDF\u4E00 `xc.` \u524D\u7F00; \u4FE1\u5C01 `{ type, v, id?, payload? }`\u3002\n *\n * \u901A\u9053:\n * 1. \u63E1\u624B: iframe \u2192 \u5BBF\u4E3B `xc.ready` (window.postMessage, targetOrigin = \u5BBF\u4E3B origin)\n * \u5BBF\u4E3B \u2192 iframe `xc.init` (window.postMessage, targetOrigin = iframe origin, \u8F6C\u79FB MessagePort)\n * 2. \u63E1\u624B\u540E\u5168\u90E8\u8D70 MessageChannel \u7AEF\u53E3 (\u7AEF\u53E3\u672C\u8EAB\u5373\u51ED\u8BC1, \u4E0D\u518D\u4F9D\u8D56 `*`)\n */\n\n/** \u534F\u8BAE\u7248\u672C; \u4E0D\u517C\u5BB9\u53D8\u66F4\u65F6 +1\u3002 */\nexport const XC_PROTOCOL_VERSION = 1 as const;\n\n/** \u5B98\u65B9\u90E8\u7F72\u7684 origin / embed \u5165\u53E3\u3002 */\nexport const XC_DEFAULT_ORIGIN = 'https://xiaochun.firetable.tech';\nexport const XC_DEFAULT_SRC = `${XC_DEFAULT_ORIGIN}/embed`;\n\n/** \u5BBF\u4E3B \u2192 iframe \u547D\u4EE4\u3002 */\nexport const XC_HOST_TO_FRAME = [\n 'xc.init',\n 'xc.say',\n 'xc.audio',\n 'xc.audio.chunk',\n 'xc.audio.end',\n 'xc.motion',\n 'xc.expression',\n 'xc.lookAt',\n 'xc.pointer',\n 'xc.setModel',\n 'xc.setOutfit',\n 'xc.setScene',\n 'xc.prefetch',\n 'xc.setConfig',\n 'xc.mic',\n 'xc.pause',\n 'xc.resume',\n 'xc.destroy',\n] as const;\n\n/** iframe \u2192 \u5BBF\u4E3B \u4E8B\u4EF6\u3002 */\nexport const XC_FRAME_TO_HOST = [\n 'xc.ready',\n 'xc.load.progress',\n 'xc.loaded',\n 'xc.outfit-changed',\n 'xc.scene-changed',\n 'xc.prefetched',\n 'xc.state',\n 'xc.stt',\n 'xc.utterance',\n 'xc.hit-region',\n 'xc.gesture-move',\n 'xc.gesture-resize',\n 'xc.error',\n] as const;\n\nexport type XcHostMessageType = (typeof XC_HOST_TO_FRAME)[number];\nexport type XcFrameMessageType = (typeof XC_FRAME_TO_HOST)[number];\n\n/** \u5F53\u524D /embed \u5DF2\u5B9E\u73B0\u7684\u547D\u4EE4 (xc.init \u662F\u63E1\u624B, \u4E0D\u8BA1\u5165)\u3002 */\nexport const XC_IMPLEMENTED_COMMANDS = [\n 'xc.say',\n 'xc.audio',\n 'xc.audio.chunk',\n 'xc.audio.end',\n 'xc.motion',\n 'xc.expression',\n 'xc.pointer',\n 'xc.setModel',\n 'xc.setOutfit',\n 'xc.setScene',\n 'xc.prefetch',\n 'xc.setConfig',\n 'xc.mic',\n 'xc.pause',\n 'xc.resume',\n 'xc.destroy',\n] as const satisfies readonly XcHostMessageType[];\n\n/**\n * \u6682\u4E0D\u652F\u6301 (\u5E95\u5C42\u65E0\u5BF9\u5E94\u80FD\u529B), /embed \u56DE `xc.error{code:'unsupported'}`\u3002\n * TODO(xc.lookAt): \u89C6\u7EBF\u76EE\u524D\u7531\u76F8\u673A + \u968F\u673A\u626B\u89C6\u9A71\u52A8 (GazeController), \u6CA1\u6709\"\u5916\u90E8\u6307\u5B9A\u6CE8\u89C6\u70B9\"\u7684\u5165\u53E3;\n * \u9700\u8981\u5148\u5728 gaze \u5C42\u52A0 overrideTarget \u518D\u5F00\u653E\u3002\n */\nexport const XC_UNSUPPORTED_COMMANDS = ['xc.lookAt'] as const satisfies readonly XcHostMessageType[];\n\n/**\n * xc.* \u2194 xiaochun:// \u5BF9\u7167\u8868 (\u540C\u4E00\u8BED\u4E49\u7684\u4E24\u4E2A\u4F20\u8F93\u5C42, \u4E3B\u5E94\u7528\u5185\u8D70\u540C\u4E00\u4E2A handler: src/core/protocol/handler.ts)\u3002\n * - `action`: \u5185\u90E8 protocol action \u5BF9\u8C61 (src/core/protocol/types.ts); null = \u6CA1\u6709 deep link \u5BF9\u5E94\u7269\u3002\n * - `url`: \u5BF9\u5E94\u7684 deep link \u5F62\u5F0F (OS \u7EA7, \u53EA\u5728 Tauri \u58F3\u751F\u6548; iframe \u4E0D\u4F1A\u54CD\u5E94 xiaochun://)\u3002\n */\nexport const XC_PROTOCOL_MAPPING = [\n { xc: 'xc.say', note: 'mode:\"speak\" (\u9ED8\u8BA4)', action: 'speak', url: 'xiaochun://speak?text=\u2026' },\n { xc: 'xc.say', note: 'mode:\"chat\" (\u8D70 LLM)', action: null, url: null },\n { xc: 'xc.audio', note: 'source \u4E3A URL \u5B57\u7B26\u4E32', action: 'speak', url: 'xiaochun://speak?audioUrl=\u2026[&text=\u2026]' },\n { xc: 'xc.audio', note: 'source \u4E3A ArrayBuffer / Blob', action: 'audio', url: null },\n { xc: 'xc.audio.chunk / xc.audio.end', note: '\u6D41\u5F0F PCM', action: 'audio', url: null },\n] as const;\n\nexport type XcErrorCode =\n | 'unsupported' // \u547D\u4EE4/\u53C2\u6570\u5B58\u5728\u4F46\u5E95\u5C42\u6682\u65E0\u80FD\u529B\n | 'bad_request' // \u53C2\u6570\u7F3A\u5931/\u975E\u6CD5\n | 'not_ready' // \u6A21\u578B\u5C1A\u672A\u52A0\u8F7D\u5B8C\u6210\n | 'busy' // \u88AB\u540C\u7C7B\u66F4\u65B0\u7684\u8BF7\u6C42\u53D6\u4EE3 (\u6362\u88C5 last-wins: \u6392\u961F\u4E2D\u7684\u65E7\u8BF7\u6C42\u88AB\u65B0\u8BF7\u6C42\u9876\u6389); \u53EF\u5FFD\u7565, \u4EE5\u6700\u65B0\u4E00\u6B21\u4E3A\u51C6\n | 'unknown_id' // outfit / scene id \u4E0D\u5728 capabilities \u5217\u8868\u91CC (\u542B\u539F\u578B\u952E\u5982 constructor)\n | 'origin_denied' // \u63E1\u624B\u6765\u6E90\u4E0D\u5728\u767D\u540D\u5355\n | 'failed'; // \u6267\u884C\u671F\u5F02\u5E38\n\n/**\n * outfit / scene id \u7684\u7EDF\u4E00\u683C\u5F0F: \u5C0F\u5199\u5B57\u6BCD\u5F00\u5934, \u5C0F\u5199\u5B57\u6BCD/\u6570\u5B57/\u4E0B\u5212\u7EBF, \u6700\u957F 64\u3002\n * \u6240\u6709 id \u5165\u53E3 (xc.setOutfit / xc.setScene / ?outfit= / ?scene=) \u90FD\u5148\u8FC7\u8FD9\u6761\u6B63\u5219, \u518D\u7528 hasOwnProperty \u67E5\u8868,\n * \u8FD9\u6837 `__proto__` / `constructor` / `toString` \u8FD9\u7C7B\u539F\u578B\u952E\u6C38\u8FDC\u4E0D\u4F1A\u88AB\u5F53\u6210\u5408\u6CD5 id\u3002\n */\n/**\n * iframe \u5185\u7F6E\u754C\u9762\u90E8\u4EF6\u540D (`?ui=chat,outfit,scene` / SDK `ui: [...]`)\u3002\n * chat \u5E95\u90E8\u804A\u5929\u680F (ChatBar, \u5BF9\u8BDD\u8D70 WebLLM)\n * bubble \u5934\u9876\u6C14\u6CE1 (\u8BF4\u8BDD\u6587\u672C / \u72B6\u6001)\n * outfit \u6362\u88C5\u6309\u94AE (\u4E0E\u4E3B\u7AD9 TopHeader \u540C\u6B3E)\n * scene \u6362\u573A\u666F(\u80CC\u666F)\u6309\u94AE (\u4E0E\u4E3B\u7AD9 TopHeader \u540C\u6B3E)\n */\n/**\n * \u7A97\u53E3\u5706\u89D2\u534A\u5F84 (CSS px)\u3002Tauri \u684C\u5BA0\u7684\u65E0\u6846\u7A97\u53E3\u5706\u89D2 (src/styles/main.css \u7684 --xc-window-radius) \u4E0E /embed \u975E\u900F\u660E\u573A\u666F\u7684\u9ED8\u8BA4\u5916\u58F3\u5706\u89D2\u5171\u7528\u8FD9\u4E00\u4E2A\u503C,\n * \u6709\u5355\u6D4B\u4FDD\u8BC1\u4E24\u5904\u4E0D\u6F02\u79FB\u3002\u89D2\u843D\u5F27\u7EBF (components/CornerHandle) \u7684\u51E0\u4F55\u5C31\u662F\u6309\u8FD9\u4E2A\u534A\u5F84\u753B\u7684\u3002\n */\nexport const XC_WINDOW_CORNER_RADIUS = 20;\n\nexport const XC_UI_PARTS = ['chat', 'bubble', 'outfit', 'scene'] as const;\nexport type XcUiPart = (typeof XC_UI_PARTS)[number];\nconst XC_UI_PART_SET: ReadonlySet<string> = new Set(XC_UI_PARTS);\n/** 0.1.14 \u91CC `ui=1` / `ui: true` \u7684\u542B\u4E49 = \u5F53\u65F6\u5B58\u5728\u7684\u5168\u90E8\u90E8\u4EF6\u3002 */\nexport const XC_UI_LEGACY_PARTS: readonly XcUiPart[] = ['chat', 'bubble'];\n\nexport interface XcUiParse {\n /** \u767D\u540D\u5355\u5185\u7684\u90E8\u4EF6 (\u53BB\u91CD, \u4FDD\u6301 XC_UI_PARTS \u987A\u5E8F)\u3002 */\n parts: XcUiPart[];\n /** \u88AB\u5FFD\u7565\u7684\u672A\u77E5\u9879 (\u8C03\u7528\u65B9\u8D1F\u8D23 warn)\u3002 */\n unknown: string[];\n /** \u7528\u4E86\u5DF2\u5F03\u7528\u7684 1 / true \u5199\u6CD5 (\u8C03\u7528\u65B9\u8D1F\u8D23 warn)\u3002 */\n legacy: boolean;\n}\n\n/** \u89C4\u6574\u90E8\u4EF6\u540D\u5217\u8868: \u975E\u5B57\u7B26\u4E32 / \u672A\u77E5\u540D\u5B57\u5FFD\u7565\u5E76\u8BB0\u5165 unknown, \u91CD\u590D\u53BB\u91CD\u3002 */\nexport function parseXcUiList(items: readonly unknown[]): XcUiParse {\n const seen = new Set<string>();\n const unknown: string[] = [];\n for (const it of items) {\n const name = typeof it === 'string' ? it.trim().toLowerCase() : '';\n if (XC_UI_PART_SET.has(name)) seen.add(name);\n else unknown.push(typeof it === 'string' ? it.slice(0, 40) : String(typeof it));\n }\n return { parts: XC_UI_PARTS.filter((p) => seen.has(p)), unknown, legacy: false };\n}\n\n/** `?ui=` \u53C2\u6570 / \u5143\u7D20\u5C5E\u6027: \u9017\u53F7\u5206\u9694\u7684\u90E8\u4EF6\u540D; \u7F3A\u7701 / \u7A7A / 0 / false = \u5168\u4E0D\u663E\u793A; 1 / true = \u65E7\u5199\u6CD5 (\u5F03\u7528, \u6620\u5C04\u4E3A chat + bubble)\u3002 */\nexport function parseXcUiParam(raw: string | null | undefined): XcUiParse {\n const v = (raw ?? '').trim().toLowerCase();\n if (v === '' || v === '0' || v === 'false') return { parts: [], unknown: [], legacy: false };\n if (v === '1' || v === 'true') return { parts: [...XC_UI_LEGACY_PARTS], unknown: [], legacy: true };\n return parseXcUiList(v.split(',').filter((x) => x.trim() !== ''));\n}\n\n/** SDK \u9009\u9879 `ui` (\u6570\u7EC4, \u6216\u5F03\u7528\u7684\u5E03\u5C14) \u2192 \u89C4\u6574\u7ED3\u679C\u3002 */\nexport function normalizeXcUiOption(v: unknown): XcUiParse {\n if (v === true) return { parts: [...XC_UI_LEGACY_PARTS], unknown: [], legacy: true };\n if (Array.isArray(v)) return parseXcUiList(v);\n return { parts: [], unknown: [], legacy: false };\n}\n\nexport const XC_ID_RE = /^[a-z][a-z0-9_]{0,63}$/;\nexport const isXcId = (v: unknown): v is string => typeof v === 'string' && XC_ID_RE.test(v);\n/** \u53EA\u8BA4\u5BF9\u8C61\u81EA\u5DF1\u7684\u5C5E\u6027 (\u4E0D\u8D70\u539F\u578B\u94FE); \u7B49\u4EF7\u4E8E Object.hasOwn, \u4F46\u517C\u5BB9 ES2020 \u76EE\u6807\u3002 */\nexport const xcHasOwn = (o: object, k: string): boolean => Object.prototype.hasOwnProperty.call(o, k);\n\n/** \u5185\u7F6E\u573A\u666F id (\u4EE5 xc.ready.capabilities.scenes \u4E3A\u51C6; \u76EE\u524D\u5C31\u8FD9\u4E09\u4E2A\u4E3B\u9898, \u4E0D\u542B\u771F\u5B9E\u573A\u666F\u8D44\u4EA7)\u3002 */\nexport type XcSceneId = 'light' | 'dark' | 'transparent';\nexport type XcLang = 'zh-CN' | 'en' | 'ja';\nexport type XcPhase = 'idle' | 'loading' | 'thinking' | 'speaking' | 'listening' | 'paused';\n/** lazy = \u4E0D\u9884\u70ED WebLLM / EMAGE, \u9996\u6B21\u4E92\u52A8\u518D\u52A0\u8F7D (\u9ED8\u8BA4); eager = \u7ACB\u5373\u9884\u70ED\u3002 */\nexport type XcHeavyMode = 'lazy' | 'eager';\n\nexport interface XcEnvelope<T extends string = string, P = unknown> {\n type: T;\n v: typeof XC_PROTOCOL_VERSION;\n /** \u5BBF\u4E3B\u547D\u4EE4\u53EF\u5E26 id; \u5BF9\u5E94\u7684 xc.error / xc.utterance \u4F1A\u56DE\u5E26\u540C\u4E00\u4E2A id\u3002 */\n id?: string;\n payload: P;\n}\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u5BBF\u4E3B \u2192 iframe payload \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nexport interface XcConfig {\n lang?: XcLang;\n /** \u80CC\u666F\u900F\u660E (\u53E0\u5728\u5BBF\u4E3B\u9875\u9762\u4E0A)\u3002 */\n transparent?: boolean;\n /**\n * \u8981\u663E\u793A\u7684 iframe \u5185\u7F6E\u754C\u9762\u90E8\u4EF6 (\u89C1 XC_UI_PARTS), \u4F8B\u5982 ['outfit','scene']; \u7701\u7565 / \u7A7A\u6570\u7EC4 = \u4E0D\u663E\u793A\u3002\n * \u4F20 true/false \u662F 0.1.14 \u7684\u65E7\u5199\u6CD5 (true = chat + bubble), \u5DF2\u5F03\u7528, \u4EC5\u4E3A\u517C\u5BB9\u4FDD\u7559\u3002\n */\n ui?: XcUiPart[] | boolean;\n /** \u91CD\u8D44\u6E90\u52A0\u8F7D\u7B56\u7565, \u89C1 XcHeavyMode\u3002 */\n heavy?: XcHeavyMode;\n /**\n * \u5141\u8BB8 xc.setModel \u52A0\u8F7D\u4EFB\u610F https URL \u7684 .vrm/.vrmaddon/.vrmbase\u3002\u9ED8\u8BA4 false (\u5173\u95ED): \u53EA\u80FD\u7528 capabilities.outfits \u91CC\u7684\u5185\u7F6E\u670D\u88C5\u3002\n * \u7531\u5BBF\u4E3B SDK \u7684 `allowCustomModel` \u9009\u9879\u53D1\u51FA (\u653E\u5728 xc.init \u7684 config \u91CC)\u3002\n */\n allowCustomModel?: boolean;\n /**\n * \u624B\u52BF\u5F00\u5173 (\u5BBF\u4E3B SDK \u7684 `draggable` / `resizable` \u9009\u9879\u53D1\u51FA; \u653E\u5728 xc.init \u7684 config \u91CC, \u4E5F\u53EF\u7528 xc.setConfig \u8FD0\u884C\u65F6\u6539)\u3002\n * \u9ED8\u8BA4\u5168 false: iframe \u91CC**\u4E0D\u8BC6\u522B**\u5BF9\u5E94\u624B\u52BF\u3001\u4E5F\u4E0D\u62E6\u622A\u4EFB\u4F55\u6307\u9488\u4E8B\u4EF6\u3002\u4EC5\u5F53 capabilities.gestures \u5B58\u5728\u65F6\u624D\u6709\u610F\u4E49 (\u65E7\u7248 /embed \u5FFD\u7565)\u3002\n * move = \u5DE6\u952E\u5728\u89D2\u8272\u4E0A\u62D6\u52A8 \u2192 \u53D1 xc.gesture-move, \u7531\u5BBF\u4E3B\u79FB\u52A8 iframe\n * resize = \u5728 iframe \u56DB\u89D2\u70ED\u533A\u6309\u4E0B\u62D6\u52A8 \u2192 \u53D1 xc.gesture-resize, \u7531\u5BBF\u4E3B\u7F29\u653E iframe\n */\n gestures?: { move?: boolean; resize?: boolean };\n}\n\nexport interface XcInitPayload {\n /** \u5BBF\u4E3B\u58F0\u660E\u7684\u81EA\u8EAB origin (iframe \u4F1A\u4E0E event.origin \u4EA4\u53C9\u6821\u9A8C)\u3002 */\n hostOrigin: string;\n config?: XcConfig;\n}\n\nexport interface XcSayPayload {\n text: string;\n /**\n * 'speak' (\u9ED8\u8BA4): \u76F4\u63A5 TTS + \u52A8\u4F5C, \u4E0D\u7ECF\u8FC7 LLM\u3002\n * 'chat': \u5F53\u4F5C\u7528\u6237\u8F93\u5165\u8D70 WebLLM / \u81EA\u5B9A\u4E49 provider (\u4F1A\u89E6\u53D1\u5927\u6A21\u578B\u52A0\u8F7D)\u3002\n */\n mode?: 'speak' | 'chat';\n}\n\n/** \u5BBF\u4E3B\u97F3\u9891\u7684\u7F16\u7801: encoded = \u5BB9\u5668\u683C\u5F0F (mp3/wav/ogg/aac/webm\u2026 \u7531\u6D4F\u89C8\u5668 decodeAudioData \u51B3\u5B9A); pcm16 / float32 = \u65E0\u5934\u539F\u59CB PCM (\u5C0F\u7AEF, \u4EA4\u9519)\u3002 */\nexport type XcAudioFormat = 'encoded' | 'pcm16' | 'float32';\n\nexport interface XcAudioOptions {\n /** \u6C14\u6CE1\u91CC\u663E\u793A\u7684\u6587\u5B57 (\u53EF\u9009; \u4E0D\u5F71\u54CD\u97F3\u9891\u4E0E\u52A8\u4F5C)\u3002 */\n text?: string;\n /** \u662F\u5426\u7531 EMAGE \u6839\u636E\u8FD9\u6BB5\u97F3\u9891\u751F\u6210\u5168\u8EAB\u52A8\u4F5C, \u9ED8\u8BA4 true\u3002false = \u53EA\u64AD\u653E\u97F3\u9891 (\u6B64\u65F6\u5B8C\u5168\u4E0D\u52A0\u8F7D EMAGE)\u3002 */\n motion?: boolean;\n /** \u662F\u5426\u9A71\u52A8\u53E3\u578B (\u97F3\u91CF RMS \u2192 'aa'), \u9ED8\u8BA4 true\u3002 */\n lipsync?: boolean;\n}\n\n/**\n * xc.audio \u2014 \u6574\u6BB5\u97F3\u9891: iframe \u5185\u89E3\u7801 \u2192 16 kHz \u2192 EMAGE \u7A97\u53E3\u63A8\u7406 + \u540C\u4E00\u6BB5\u97F3\u9891\u64AD\u653E + \u53E3\u578B, \u64AD\u5B8C\u56DE xc.utterance{phase:'end'}\u3002\n * \u4FE1\u5C01 `id` \u7528\u4E8E\u5173\u8054 xc.utterance / xc.error\u3002\u5927 ArrayBuffer \u8BF7\u653E\u8FDB postMessage \u7684 transfer \u5217\u8868 (SDK \u9ED8\u8BA4\u8FD9\u4E48\u505A)\u3002\n */\nexport interface XcAudioPayload extends XcAudioOptions {\n /** ArrayBuffer / Blob (structured clone) \u6216 URL \u5B57\u7B26\u4E32 (iframe \u5185 fetch, \u4EC5 https \u6216\u540C\u6E90, \u9700 CORS)\u3002 */\n source: ArrayBuffer | Blob | string;\n /** MIME \u63D0\u793A, \u5982 'audio/mpeg'\u3002\u4EC5\u4F5C\u4FE1\u606F, \u89E3\u7801\u4EE5\u6D4F\u89C8\u5668\u55C5\u63A2\u4E3A\u51C6\u3002 */\n mimeType?: string;\n /** \u9ED8\u8BA4 'encoded'\u3002 */\n format?: XcAudioFormat;\n /** \u4EC5\u539F\u59CB PCM \u9700\u8981 (\u9ED8\u8BA4 16000); \u5BB9\u5668\u683C\u5F0F\u5FFD\u7565 (\u4EE5\u6587\u4EF6\u5185\u7684\u91C7\u6837\u7387\u4E3A\u51C6)\u3002\u8303\u56F4 8000~96000\u3002 */\n sampleRate?: number;\n /** \u539F\u59CB PCM \u7684\u58F0\u9053\u6570 (\u4EA4\u9519), 1 \u6216 2, \u9ED8\u8BA4 1; \u5185\u90E8\u4E0B\u6DF7\u4E3A\u5355\u58F0\u9053\u3002 */\n channels?: 1 | 2;\n}\n\n/**\n * xc.audio.chunk \u2014 \u6D41\u5F0F\u97F3\u9891\u5206\u5757 (\u539F\u59CB PCM)\u3002\u4FE1\u5C01 `id` = \u6D41 id: \u540C\u4E00\u4E2A id \u7684\u7B2C\u4E00\u4E2A chunk \u5F00\u542F\u4E00\u6B21\u8BF4\u8BDD (\u56DE xc.utterance start),\n * \u4E4B\u540E\u7684 chunk \u8FFD\u52A0; \u4EE5 xc.audio.end \u6536\u5C3E\u3002\u6BCF\u4E2A chunk \u7684 `data` \u5E94\u653E\u8FDB transfer \u5217\u8868\u3002\n * \u9009\u9879 (text/motion/lipsync) \u53EA\u5728\u7B2C\u4E00\u4E2A chunk \u91CC\u751F\u6548; sampleRate/format \u6574\u6761\u6D41\u5FC5\u987B\u4E00\u81F4\u3002\n */\nexport interface XcAudioChunkPayload extends XcAudioOptions {\n data: ArrayBuffer;\n format: Exclude<XcAudioFormat, 'encoded'>;\n /** 8000~96000\u3002 */\n sampleRate: number;\n channels?: 1 | 2;\n}\n\nexport interface XcAudioEndPayload {\n /** true = \u7ACB\u5373\u6253\u65AD (\u4E22\u5F03\u672A\u64AD\u653E\u7684\u97F3\u9891); \u9ED8\u8BA4 false = \u64AD\u5B8C\u5DF2\u6536\u5230\u7684\u97F3\u9891\u540E\u7ED3\u675F\u3002 */\n abort?: boolean;\n}\n\nexport interface XcMotionPayload {\n /** .vrma URL (https:// \u6216\u540C\u6E90\u8DEF\u5F84)\u3002\u4E0E stop \u4E8C\u9009\u4E00\u3002 */\n url?: string;\n /** \u5185\u7F6E\u52A8\u4F5C\u540D, \u76EE\u524D\u4EC5 'thinking'\u3002 */\n name?: 'thinking';\n stop?: boolean;\n loop?: boolean;\n /** \u6DE1\u5165\u6DE1\u51FA\u79D2\u6570, \u8303\u56F4 0.26~3, \u8C03\u5927\u66F4\u67D4\u548C\u4F46\u54CD\u5E94\u66F4\u6162\u3002 */\n fadeDuration?: number;\n /** \u64AD\u653E\u500D\u901F, \u8303\u56F4 0.25~3\u3002 */\n timeScale?: number;\n mask?: 'all' | 'upperBody';\n}\n\nexport interface XcExpressionPayload {\n name: 'neutral' | 'happy' | 'angry' | 'sad' | 'relaxed' | 'surprised';\n}\n\n/** xc.lookAt \u2014 TODO: \u6682\u4E0D\u652F\u6301, \u6536\u5230\u4F1A\u8FD4\u56DE unsupported\u3002 */\nexport interface XcLookAtPayload {\n /** \u5F52\u4E00\u5316 (-1..1), \u76F8\u5BF9 iframe \u4E2D\u5FC3\u3002 */\n x: number;\n y: number;\n}\n\nexport interface XcPointerPayload {\n /** iframe \u5185 client \u5750\u6807 (px)\u3002SDK \u4F1A\u628A\u5BBF\u4E3B pointer \u4E8B\u4EF6\u6362\u7B97\u597D\u518D\u53D1\u3002 */\n x: number;\n y: number;\n}\n\nexport interface XcSetModelPayload {\n /** \u5B8C\u6574 .vrm / .vrmaddon / .vrmbase URL (\u9700 CORS)\u3002**\u9ED8\u8BA4\u5173\u95ED**, \u9700\u8981 xc.init config.allowCustomModel=true, \u5426\u5219\u56DE unsupported\u3002 */\n url?: string;\n /** \u5185\u7F6E\u670D\u88C5 id (capabilities.outfits \u91CC\u7684 id, \u5982 'xiaochun_maid')\u3002\u88F8\u6A21 'base' \u4E0D\u5BF9\u5916\u5F00\u653E (\u56DE unknown_id)\u3002\u65B0\u4EE3\u7801\u8BF7\u7528 xc.setOutfit\u3002 */\n outfit?: string;\n name?: string;\n}\n\n/** xc.setOutfit \u2014 \u6362\u5185\u7F6E\u670D\u88C5 (id \u6765\u81EA xc.ready.capabilities.outfits)\u3002\u4E32\u884C + last-wins, \u89C1 docs/EMBED.md\u3002 */\nexport interface XcSetOutfitPayload {\n id: string;\n}\n\n/**\n * xc.prefetch \u2014 \u9884\u53D6\u670D\u88C5\u8D44\u6E90\u5230 iframe \u7684 IndexedDB (\u4E4B\u540E xc.setOutfit \u76F4\u63A5\u547D\u4E2D\u7F13\u5B58, \u4E0D\u8D70\u7F51\u7EDC)\u3002\u9ED8\u8BA4\u4E0D\u4F1A\u81EA\u52A8\u53D1\u751F\u3002\n * \u5168\u5C40\u4E32\u884C (\u5E76\u53D1 1); \u5F00\u4E86 heavy:'eager' \u65F6\u6392\u5728 EMAGE \u52A0\u8F7D\u4E4B\u540E, \u4E5F\u4E0D\u4F1A\u548C\u8FDB\u884C\u4E2D\u7684\u6362\u88C5\u62A2\u5E26\u5BBD\u3002\n * ids \u7701\u7565 = \u5168\u90E8\u5185\u7F6E\u670D\u88C5, **\u9664\u4E86**\u4F53\u79EF\u5927\u7684\u5A5A\u7EB1 (xiaochun_wedding, 13.9MB); \u663E\u5F0F\u70B9\u540D\u5219\u7167\u505A\u3002\n * \u5E94\u7B54 xc.prefetched (\u5E26\u540C\u4E00\u4E2A\u4FE1\u5C01 id)\u3002\n */\nexport interface XcPrefetchPayload {\n ids?: string[];\n}\n\n/** xc.prefetched \u2014 \u9884\u53D6\u5B8C\u6210 (\u9010\u9879\u6210\u8D25\u4E0D\u5F71\u54CD\u6574\u4F53; failed \u91CC\u7684 id \u53EF\u7A0D\u540E\u91CD\u8BD5)\u3002 */\nexport interface XcPrefetchedPayload {\n downloaded: string[];\n cached: string[];\n failed: string[];\n /** 'save-data' = \u7528\u6237\u5F00\u4E86\u7701\u6D41\u91CF\u6A21\u5F0F, \u6574\u4E2A\u8BF7\u6C42\u88AB\u8DF3\u8FC7\u3002 */\n skipped?: 'save-data';\n}\n\n/** xc.setScene \u2014 \u8FD0\u884C\u65F6\u5207\u573A\u666F (id \u6765\u81EA xc.ready.capabilities.scenes: light / dark / transparent)\u3002 */\nexport interface XcSetScenePayload {\n id: string;\n}\n\nexport interface XcMicPayload {\n enabled: boolean;\n}\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 iframe \u2192 \u5BBF\u4E3B payload \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nexport interface XcReadyPayload {\n version: string;\n protocol: typeof XC_PROTOCOL_VERSION;\n capabilities: {\n commands: string[];\n unsupported: string[];\n stt: boolean;\n transparent: boolean;\n /** \u5BBF\u4E3B\u97F3\u9891\u80FD\u529B (xc.audio / xc.audio.chunk)\u3002\u65E7\u7248 /embed \u6CA1\u6709\u8FD9\u4E2A\u5B57\u6BB5\u3002 */\n audio?: { formats: XcAudioFormat[]; streaming: boolean; maxSeconds: number };\n /** iframe \u5185 `self.crossOriginIsolated` (true = \u53EF\u7528 SharedArrayBuffer / \u591A\u7EBF\u7A0B wasm)\u3002\u65E7\u7248 /embed \u6CA1\u6709\u8FD9\u4E2A\u5B57\u6BB5\u3002\u4EC5\u8BCA\u65AD\u7528\u3002 */\n crossOriginIsolated?: boolean;\n /** \u53EF\u6362\u7684\u5185\u7F6E\u670D\u88C5 (\u4E0D\u542B\u88F8\u6A21)\u3002\u65E7\u7248 /embed \u6CA1\u6709\u8FD9\u4E2A\u5B57\u6BB5 \u2192 \u5BBF\u4E3B\u5E94\u89C6\u4E3A\"\u4E0D\u652F\u6301 xc.setOutfit\"\u3002 */\n outfits?: XcOutfitInfo[];\n /** \u53EF\u5207\u7684\u573A\u666F\u3002\u65E7\u7248 /embed \u6CA1\u6709\u8FD9\u4E2A\u5B57\u6BB5 \u2192 \u89C6\u4E3A\"\u4E0D\u652F\u6301 xc.setScene\"\u3002 */\n scenes?: XcSceneInfo[];\n /** \u652F\u6301 xc.prefetch\u3002\u65E7\u7248 /embed \u6CA1\u6709\u8FD9\u4E2A\u5B57\u6BB5\u3002 */\n prefetch?: boolean;\n /**\n * iframe \u5185\u624B\u52BF (\u62D6\u52A8 / \u89D2\u843D\u7F29\u653E) \u652F\u6301\u60C5\u51B5; \u65E7\u7248 /embed \u6CA1\u6709\u8FD9\u4E2A\u5B57\u6BB5 \u2192 \u5BBF\u4E3B\u89C6\u4E3A\"\u4E0D\u652F\u6301\", draggable / resizable \u65E0\u6548\u3002\n * cornerSize = \u56DB\u89D2\u70ED\u533A\u8FB9\u957F (CSS px, iframe \u89C6\u53E3\u5DE6\u4E0A/\u53F3\u4E0A/\u5DE6\u4E0B/\u53F3\u4E0B\u5404\u4E00\u4E2A\u6B63\u65B9\u5F62), \u5BBF\u4E3B UI \u53EF\u636E\u6B64\u63D0\u793A\u3002\n */\n gestures?: { move: boolean; resize: boolean; cornerSize: number };\n };\n}\n\nexport interface XcOutfitInfo {\n id: string;\n /** \u5C55\u793A\u540D (\u82F1\u6587, \u6765\u81EA\u4E3B\u4ED3\u5E93\u914D\u7F6E)\u3002 */\n name: string;\n /** \u670D\u88C5\u6587\u4EF6\u5927\u5C0F\u63D0\u793A (MB, \u5341\u8FDB\u5236, \u7EA6\u503C; \u516C\u5171\u7684\u88F8\u6A21 ~5.7MB \u4E0D\u8BA1)\u3002\u4EC5\u4F9B\u5BBF\u4E3B\u81EA\u5DF1\u7684 UI \u53C2\u8003 (\u5185\u7F6E\u6362\u88C5\u83DC\u5355\u4E0D\u663E\u793A\u4F53\u79EF); \u65E7\u7248 /embed \u6CA1\u6709\u8FD9\u4E2A\u5B57\u6BB5\u3002 */\n sizeMB?: number;\n}\n\nexport interface XcSceneInfo {\n id: string;\n /** true = \u80CC\u666F\u900F\u660E (\u53E0\u5728\u5BBF\u4E3B\u9875\u4E0A, \u53EF\u5F00\u7A7F\u900F)\u3002 */\n transparent: boolean;\n}\n\nexport interface XcLoadProgressPayload {\n /**\n * 'model' = \u9996\u6B21\u52A0\u8F7D; 'outfit' = \u8FD0\u884C\u4E2D\u6362\u88C5 (id = \u76EE\u6807\u670D\u88C5 id, \u81EA\u5B9A\u4E49 URL \u65F6\u7701\u7565);\n * 'prefetch' = \u5BBF\u4E3B\u53D1\u4E86 xc.prefetch \u540E\u7684\u540E\u53F0\u4E0B\u8F7D (id = \u6B63\u5728\u9884\u53D6\u7684\u670D\u88C5)\u3002\u5DF2\u6709\u7684\"model \u8FDB\u5EA6\u6761\"\u903B\u8F91\u8BF7\u5148\u5224\u65AD phase\u3002\n */\n phase: 'model' | 'outfit' | 'prefetch';\n /** 0~100 */\n progress: number;\n id?: string;\n}\n\nexport interface XcLoadedPayload {\n model: string;\n}\n\n/**\n * xc.outfit-changed \u2014 \u670D\u88C5\u5DF2\u751F\u6548\u3002\u4E09\u79CD\u6765\u6E90: \u9996\u6B21\u52A0\u8F7D (initial:true)\u3001xc.setOutfit / xc.setModel \u7684\u5E94\u7B54 (\u5E26\u540C\u4E00\u4E2A\u4FE1\u5C01 id)\u3002\n * noop:true = \u76EE\u6807\u5DF2\u7ECF\u662F\u5F53\u524D\u670D\u88C5 (\u6216\u4E0E\u53E6\u4E00\u4E2A\u540C\u76EE\u6807\u8BF7\u6C42\u5408\u5E76), \u6CA1\u6709\u91CD\u65B0\u52A0\u8F7D; SDK \u53EA\u7528\u5B83 resolve \u5BF9\u5E94\u7684 Promise, \u4E0D\u518D\u89E6\u53D1\u4E8B\u4EF6\u3002\n */\nexport interface XcOutfitChangedPayload {\n /** \u5185\u7F6E\u670D\u88C5 id; \u81EA\u5B9A\u4E49 URL \u6A21\u578B\u4E3A null\u3002 */\n id: string | null;\n name: string;\n previous?: string | null;\n initial?: boolean;\n noop?: boolean;\n}\n\n/** xc.scene-changed \u2014 \u573A\u666F\u5DF2\u751F\u6548 (\u9996\u6B21\u63E1\u624B\u540E\u7684\u5F53\u524D\u573A\u666F / xc.setScene \u5E94\u7B54 / xc.setConfig{transparent} \u5F15\u8D77\u7684\u5207\u6362)\u3002 */\nexport interface XcSceneChangedPayload {\n id: string;\n transparent: boolean;\n previous?: string | null;\n initial?: boolean;\n noop?: boolean;\n}\n\nexport interface XcStatePayload {\n phase: XcPhase;\n paused: boolean;\n heavy: XcHeavyMode;\n}\n\nexport type XcSttPayload =\n | { kind: 'state'; state: 'idle' | 'loading' | 'listening' | 'recognizing' | 'error' }\n | { kind: 'progress'; percent: number }\n | { kind: 'text'; text: string };\n\nexport interface XcUtterancePayload {\n phase: 'start' | 'end';\n text: string;\n /** 'text' = xc.say (TTS); 'audio' = xc.audio / xc.audio.chunk (\u5BBF\u4E3B\u97F3\u9891, \u6CA1\u6709\u8D70 TTS)\u3002\u65E7\u7248 /embed \u6CA1\u6709\u8FD9\u4E2A\u5B57\u6BB5\u3002 */\n kind?: 'text' | 'audio';\n}\n\nexport interface XcHitRegionPayload {\n hit: boolean;\n x: number;\n y: number;\n}\n\n/** \u624B\u52BF\u9636\u6BB5: start = \u6309\u4E0B\u540E\u786E\u8BA4\u6210\u624B\u52BF; move = \u589E\u91CF; end = \u7ED3\u675F (\u677E\u624B / \u53D6\u6D88 / \u5931\u7126)\u3002 */\nexport type XcGesturePhase = 'start' | 'move' | 'end';\n\n/**\n * xc.gesture-move / xc.gesture-resize \u7684\u516C\u5171\u5B57\u6BB5\u3002\u5355\u4F4D CSS px (\u5C4F\u5E55\u5750\u6807\u5DEE, iframe \u88AB\u79FB\u52A8\u65F6\u4ECD\u7A33\u5B9A)\u3002\n * - gesture: \u624B\u52BF\u5E8F\u53F7, \u6BCF\u6B21\u65B0\u624B\u52BF +1 (\u4ECE 1 \u8D77); \u5BBF\u4E3B\u636E\u6B64\u533A\u5206\u624B\u52BF, \u4E22\u5F03\u8FC7\u671F\u624B\u52BF\u7684\u8FDF\u5230\u6D88\u606F\u3002\n * - seq: \u8BE5\u624B\u52BF\u5185\u7684\u6D88\u606F\u5E8F\u53F7, start = 0, \u4E4B\u540E\u4E25\u683C\u9012\u589E; \u5BBF\u4E3B\u4E22\u5F03 seq \u4E0D\u9012\u589E\u7684\u6D88\u606F\u3002\n * - dx / dy: \u76F8\u5BF9\u4E0A\u4E00\u6761\u6D88\u606F\u7684\u589E\u91CF; totalDx / totalDy: \u76F8\u5BF9 start \u7684\u7D2F\u8BA1 (\u5BBF\u4E3B\u4EE5\u5B83\u4E3A\u51C6\u8BA1\u7B97, \u589E\u91CF\u53EA\u662F\u53C2\u8003, \u4E22\u5305\u4E5F\u4E0D\u6F02\u79FB)\u3002\n * - reason: \u4EC5 phase='end' \u5E26: up = \u677E\u624B, cancel = \u6307\u9488\u88AB\u7CFB\u7EDF\u53D6\u6D88, blur = \u7A97\u53E3\u5931\u7126\u3002\n */\nexport interface XcGestureBase {\n gesture: number;\n seq: number;\n phase: XcGesturePhase;\n dx: number;\n dy: number;\n totalDx: number;\n totalDy: number;\n reason?: 'up' | 'cancel' | 'blur';\n}\nexport type XcGestureMovePayload = XcGestureBase;\nexport type XcGestureCorner = 'NW' | 'NE' | 'SW' | 'SE';\nexport interface XcGestureResizePayload extends XcGestureBase {\n /** \u88AB\u62D6\u7684\u89D2; \u5BF9\u89D2\u56FA\u5B9A\u4E0D\u52A8\u3002 */\n corner: XcGestureCorner;\n}\n\nexport interface XcErrorPayload {\n code: XcErrorCode;\n message: string;\n /** \u89E6\u53D1\u9519\u8BEF\u7684\u547D\u4EE4\u540D (\u5982 'xc.lookAt')\u3002 */\n command?: string;\n}\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u7C7B\u578B\u6620\u5C04 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nexport interface XcHostPayloadMap {\n 'xc.init': XcInitPayload;\n 'xc.say': XcSayPayload;\n 'xc.audio': XcAudioPayload;\n 'xc.audio.chunk': XcAudioChunkPayload;\n 'xc.audio.end': XcAudioEndPayload;\n 'xc.motion': XcMotionPayload;\n 'xc.expression': XcExpressionPayload;\n 'xc.lookAt': XcLookAtPayload;\n 'xc.pointer': XcPointerPayload;\n 'xc.setModel': XcSetModelPayload;\n 'xc.setOutfit': XcSetOutfitPayload;\n 'xc.setScene': XcSetScenePayload;\n 'xc.prefetch': XcPrefetchPayload;\n 'xc.setConfig': XcConfig;\n 'xc.mic': XcMicPayload;\n 'xc.pause': undefined;\n 'xc.resume': undefined;\n 'xc.destroy': undefined;\n}\n\nexport interface XcFramePayloadMap {\n 'xc.ready': XcReadyPayload;\n 'xc.load.progress': XcLoadProgressPayload;\n 'xc.loaded': XcLoadedPayload;\n 'xc.outfit-changed': XcOutfitChangedPayload;\n 'xc.scene-changed': XcSceneChangedPayload;\n 'xc.prefetched': XcPrefetchedPayload;\n 'xc.state': XcStatePayload;\n 'xc.stt': XcSttPayload;\n 'xc.utterance': XcUtterancePayload;\n 'xc.hit-region': XcHitRegionPayload;\n 'xc.gesture-move': XcGestureMovePayload;\n 'xc.gesture-resize': XcGestureResizePayload;\n 'xc.error': XcErrorPayload;\n}\n\nexport type XcHostMessage = {\n [K in XcHostMessageType]: XcEnvelope<K, XcHostPayloadMap[K]>;\n}[XcHostMessageType];\n\nexport type XcFrameMessage = {\n [K in XcFrameMessageType]: XcEnvelope<K, XcFramePayloadMap[K]>;\n}[XcFrameMessageType];\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u5C0F\u5DE5\u5177 (SDK \u4E0E /embed \u5171\u7528) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nexport function xcMessage<K extends XcHostMessageType | XcFrameMessageType>(\n type: K,\n payload?: unknown,\n id?: string,\n): XcEnvelope<K, any> {\n const m: XcEnvelope<K, any> = { type, v: XC_PROTOCOL_VERSION, payload };\n if (id !== undefined) m.id = id;\n return m;\n}\n\n/** \u662F\u5426\u4E3A\u5408\u6CD5 xc \u4FE1\u5C01 (\u53EA\u68C0\u67E5\u7ED3\u6784, \u4E0D\u68C0\u67E5 payload)\u3002 */\nexport function isXcEnvelope(data: unknown): data is XcEnvelope {\n if (!data || typeof data !== 'object') return false;\n const d = data as Record<string, unknown>;\n return typeof d.type === 'string' && d.type.startsWith('xc.') && d.v === XC_PROTOCOL_VERSION;\n}\n\n/**\n * \u628A\u7528\u6237\u7ED9\u7684 origin \u89C4\u6574\u6210\u4E25\u683C\u7684 `scheme://host[:port]`\u3002\n * \u62D2\u7EDD '*' / \u901A\u914D / \u975E http(s) / \u5E26\u8DEF\u5F84\u7684\u5B57\u7B26\u4E32 \u2192 \u8FD4\u56DE null\u3002\n */\nexport function normalizeOrigin(input: string | null | undefined): string | null {\n if (!input || input === '*' || input === 'null') return null;\n try {\n const u = new URL(input);\n if (u.protocol !== 'https:' && u.protocol !== 'http:') return null;\n return u.origin;\n } catch {\n return null;\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaO,IAAM,sBAAsB;AAG5B,IAAM,oBAAoB;AAC1B,IAAM,iBAAiB,GAAG,iBAAiB;AAG3C,IAAM,mBAAmB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,mBAAmB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAMO,IAAM,0BAA0B;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,IAAM,0BAA0B,CAAC,WAAW;AAO5C,IAAM,sBAAsB;AAAA,EACjC,EAAE,IAAI,UAAU,MAAM,+BAAqB,QAAQ,SAAS,KAAK,+BAA0B;AAAA,EAC3F,EAAE,IAAI,UAAU,MAAM,4BAAuB,QAAQ,MAAM,KAAK,KAAK;AAAA,EACrE,EAAE,IAAI,YAAY,MAAM,wCAAoB,QAAQ,SAAS,KAAK,iDAAuC;AAAA,EACzG,EAAE,IAAI,YAAY,MAAM,oCAA+B,QAAQ,SAAS,KAAK,KAAK;AAAA,EAClF,EAAE,IAAI,iCAAiC,MAAM,oBAAU,QAAQ,SAAS,KAAK,KAAK;AACpF;AA2BO,IAAM,0BAA0B;AAEhC,IAAM,cAAc,CAAC,QAAQ,UAAU,UAAU,OAAO;AAE/D,IAAM,iBAAsC,IAAI,IAAI,WAAW;AAExD,IAAM,qBAA0C,CAAC,QAAQ,QAAQ;AAYjE,SAAS,cAAc,OAAsC;AAClE,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,UAAoB,CAAC;AAC3B,aAAW,MAAM,OAAO;AACtB,UAAM,OAAO,OAAO,OAAO,WAAW,GAAG,KAAK,EAAE,YAAY,IAAI;AAChE,QAAI,eAAe,IAAI,IAAI,EAAG,MAAK,IAAI,IAAI;AAAA,QACtC,SAAQ,KAAK,OAAO,OAAO,WAAW,GAAG,MAAM,GAAG,EAAE,IAAI,OAAO,OAAO,EAAE,CAAC;AAAA,EAChF;AACA,SAAO,EAAE,OAAO,YAAY,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,GAAG,SAAS,QAAQ,MAAM;AACjF;AAGO,SAAS,eAAe,KAA2C;AACxE,QAAM,KAAK,OAAO,IAAI,KAAK,EAAE,YAAY;AACzC,MAAI,MAAM,MAAM,MAAM,OAAO,MAAM,QAAS,QAAO,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,GAAG,QAAQ,MAAM;AAC3F,MAAI,MAAM,OAAO,MAAM,OAAQ,QAAO,EAAE,OAAO,CAAC,GAAG,kBAAkB,GAAG,SAAS,CAAC,GAAG,QAAQ,KAAK;AAClG,SAAO,cAAc,EAAE,MAAM,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,MAAM,EAAE,CAAC;AAClE;AAGO,SAAS,oBAAoB,GAAuB;AACzD,MAAI,MAAM,KAAM,QAAO,EAAE,OAAO,CAAC,GAAG,kBAAkB,GAAG,SAAS,CAAC,GAAG,QAAQ,KAAK;AACnF,MAAI,MAAM,QAAQ,CAAC,EAAG,QAAO,cAAc,CAAC;AAC5C,SAAO,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,GAAG,QAAQ,MAAM;AACjD;AAEO,IAAM,WAAW;AACjB,IAAM,SAAS,CAAC,MAA4B,OAAO,MAAM,YAAY,SAAS,KAAK,CAAC;AAEpF,IAAM,WAAW,CAAC,GAAW,MAAuB,OAAO,UAAU,eAAe,KAAK,GAAG,CAAC;AA4W7F,SAAS,UACd,MACA,SACA,IACoB;AACpB,QAAM,IAAwB,EAAE,MAAM,GAAG,qBAAqB,QAAQ;AACtE,MAAI,OAAO,OAAW,GAAE,KAAK;AAC7B,SAAO;AACT;AAGO,SAAS,aAAa,MAAmC;AAC9D,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,QAAM,IAAI;AACV,SAAO,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,WAAW,KAAK,KAAK,EAAE,MAAM;AAC3E;AAMO,SAAS,gBAAgB,OAAiD;AAC/E,MAAI,CAAC,SAAS,UAAU,OAAO,UAAU,OAAQ,QAAO;AACxD,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,KAAK;AACvB,QAAI,EAAE,aAAa,YAAY,EAAE,aAAa,QAAS,QAAO;AAC9D,WAAO,EAAE;AAAA,EACX,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/protocol.d.ts
CHANGED
|
@@ -15,13 +15,13 @@ export declare const XC_PROTOCOL_VERSION: 1;
|
|
|
15
15
|
export declare const XC_DEFAULT_ORIGIN = "https://xiaochun.firetable.tech";
|
|
16
16
|
export declare const XC_DEFAULT_SRC = "https://xiaochun.firetable.tech/embed";
|
|
17
17
|
/** 宿主 → iframe 命令。 */
|
|
18
|
-
export declare const XC_HOST_TO_FRAME: readonly ["xc.init", "xc.say", "xc.audio", "xc.audio.chunk", "xc.audio.end", "xc.motion", "xc.expression", "xc.lookAt", "xc.pointer", "xc.setModel", "xc.setConfig", "xc.mic", "xc.pause", "xc.resume", "xc.destroy"];
|
|
18
|
+
export declare const XC_HOST_TO_FRAME: readonly ["xc.init", "xc.say", "xc.audio", "xc.audio.chunk", "xc.audio.end", "xc.motion", "xc.expression", "xc.lookAt", "xc.pointer", "xc.setModel", "xc.setOutfit", "xc.setScene", "xc.prefetch", "xc.setConfig", "xc.mic", "xc.pause", "xc.resume", "xc.destroy"];
|
|
19
19
|
/** iframe → 宿主 事件。 */
|
|
20
|
-
export declare const XC_FRAME_TO_HOST: readonly ["xc.ready", "xc.load.progress", "xc.loaded", "xc.state", "xc.stt", "xc.utterance", "xc.hit-region", "xc.error"];
|
|
20
|
+
export declare const XC_FRAME_TO_HOST: readonly ["xc.ready", "xc.load.progress", "xc.loaded", "xc.outfit-changed", "xc.scene-changed", "xc.prefetched", "xc.state", "xc.stt", "xc.utterance", "xc.hit-region", "xc.gesture-move", "xc.gesture-resize", "xc.error"];
|
|
21
21
|
export type XcHostMessageType = (typeof XC_HOST_TO_FRAME)[number];
|
|
22
22
|
export type XcFrameMessageType = (typeof XC_FRAME_TO_HOST)[number];
|
|
23
23
|
/** 当前 /embed 已实现的命令 (xc.init 是握手, 不计入)。 */
|
|
24
|
-
export declare const XC_IMPLEMENTED_COMMANDS: readonly ["xc.say", "xc.audio", "xc.audio.chunk", "xc.audio.end", "xc.motion", "xc.expression", "xc.pointer", "xc.setModel", "xc.setConfig", "xc.mic", "xc.pause", "xc.resume", "xc.destroy"];
|
|
24
|
+
export declare const XC_IMPLEMENTED_COMMANDS: readonly ["xc.say", "xc.audio", "xc.audio.chunk", "xc.audio.end", "xc.motion", "xc.expression", "xc.pointer", "xc.setModel", "xc.setOutfit", "xc.setScene", "xc.prefetch", "xc.setConfig", "xc.mic", "xc.pause", "xc.resume", "xc.destroy"];
|
|
25
25
|
/**
|
|
26
26
|
* 暂不支持 (底层无对应能力), /embed 回 `xc.error{code:'unsupported'}`。
|
|
27
27
|
* TODO(xc.lookAt): 视线目前由相机 + 随机扫视驱动 (GazeController), 没有"外部指定注视点"的入口;
|
|
@@ -59,7 +59,48 @@ export declare const XC_PROTOCOL_MAPPING: readonly [{
|
|
|
59
59
|
readonly action: "audio";
|
|
60
60
|
readonly url: null;
|
|
61
61
|
}];
|
|
62
|
-
export type XcErrorCode = 'unsupported' | 'bad_request' | 'not_ready' | 'origin_denied' | 'failed';
|
|
62
|
+
export type XcErrorCode = 'unsupported' | 'bad_request' | 'not_ready' | 'busy' | 'unknown_id' | 'origin_denied' | 'failed';
|
|
63
|
+
/**
|
|
64
|
+
* outfit / scene id 的统一格式: 小写字母开头, 小写字母/数字/下划线, 最长 64。
|
|
65
|
+
* 所有 id 入口 (xc.setOutfit / xc.setScene / ?outfit= / ?scene=) 都先过这条正则, 再用 hasOwnProperty 查表,
|
|
66
|
+
* 这样 `__proto__` / `constructor` / `toString` 这类原型键永远不会被当成合法 id。
|
|
67
|
+
*/
|
|
68
|
+
/**
|
|
69
|
+
* iframe 内置界面部件名 (`?ui=chat,outfit,scene` / SDK `ui: [...]`)。
|
|
70
|
+
* chat 底部聊天栏 (ChatBar, 对话走 WebLLM)
|
|
71
|
+
* bubble 头顶气泡 (说话文本 / 状态)
|
|
72
|
+
* outfit 换装按钮 (与主站 TopHeader 同款)
|
|
73
|
+
* scene 换场景(背景)按钮 (与主站 TopHeader 同款)
|
|
74
|
+
*/
|
|
75
|
+
/**
|
|
76
|
+
* 窗口圆角半径 (CSS px)。Tauri 桌宠的无框窗口圆角 (src/styles/main.css 的 --xc-window-radius) 与 /embed 非透明场景的默认外壳圆角共用这一个值,
|
|
77
|
+
* 有单测保证两处不漂移。角落弧线 (components/CornerHandle) 的几何就是按这个半径画的。
|
|
78
|
+
*/
|
|
79
|
+
export declare const XC_WINDOW_CORNER_RADIUS = 20;
|
|
80
|
+
export declare const XC_UI_PARTS: readonly ["chat", "bubble", "outfit", "scene"];
|
|
81
|
+
export type XcUiPart = (typeof XC_UI_PARTS)[number];
|
|
82
|
+
/** 0.1.14 里 `ui=1` / `ui: true` 的含义 = 当时存在的全部部件。 */
|
|
83
|
+
export declare const XC_UI_LEGACY_PARTS: readonly XcUiPart[];
|
|
84
|
+
export interface XcUiParse {
|
|
85
|
+
/** 白名单内的部件 (去重, 保持 XC_UI_PARTS 顺序)。 */
|
|
86
|
+
parts: XcUiPart[];
|
|
87
|
+
/** 被忽略的未知项 (调用方负责 warn)。 */
|
|
88
|
+
unknown: string[];
|
|
89
|
+
/** 用了已弃用的 1 / true 写法 (调用方负责 warn)。 */
|
|
90
|
+
legacy: boolean;
|
|
91
|
+
}
|
|
92
|
+
/** 规整部件名列表: 非字符串 / 未知名字忽略并记入 unknown, 重复去重。 */
|
|
93
|
+
export declare function parseXcUiList(items: readonly unknown[]): XcUiParse;
|
|
94
|
+
/** `?ui=` 参数 / 元素属性: 逗号分隔的部件名; 缺省 / 空 / 0 / false = 全不显示; 1 / true = 旧写法 (弃用, 映射为 chat + bubble)。 */
|
|
95
|
+
export declare function parseXcUiParam(raw: string | null | undefined): XcUiParse;
|
|
96
|
+
/** SDK 选项 `ui` (数组, 或弃用的布尔) → 规整结果。 */
|
|
97
|
+
export declare function normalizeXcUiOption(v: unknown): XcUiParse;
|
|
98
|
+
export declare const XC_ID_RE: RegExp;
|
|
99
|
+
export declare const isXcId: (v: unknown) => v is string;
|
|
100
|
+
/** 只认对象自己的属性 (不走原型链); 等价于 Object.hasOwn, 但兼容 ES2020 目标。 */
|
|
101
|
+
export declare const xcHasOwn: (o: object, k: string) => boolean;
|
|
102
|
+
/** 内置场景 id (以 xc.ready.capabilities.scenes 为准; 目前就这三个主题, 不含真实场景资产)。 */
|
|
103
|
+
export type XcSceneId = 'light' | 'dark' | 'transparent';
|
|
63
104
|
export type XcLang = 'zh-CN' | 'en' | 'ja';
|
|
64
105
|
export type XcPhase = 'idle' | 'loading' | 'thinking' | 'speaking' | 'listening' | 'paused';
|
|
65
106
|
/** lazy = 不预热 WebLLM / EMAGE, 首次互动再加载 (默认); eager = 立即预热。 */
|
|
@@ -75,10 +116,28 @@ export interface XcConfig {
|
|
|
75
116
|
lang?: XcLang;
|
|
76
117
|
/** 背景透明 (叠在宿主页面上)。 */
|
|
77
118
|
transparent?: boolean;
|
|
78
|
-
/**
|
|
79
|
-
|
|
119
|
+
/**
|
|
120
|
+
* 要显示的 iframe 内置界面部件 (见 XC_UI_PARTS), 例如 ['outfit','scene']; 省略 / 空数组 = 不显示。
|
|
121
|
+
* 传 true/false 是 0.1.14 的旧写法 (true = chat + bubble), 已弃用, 仅为兼容保留。
|
|
122
|
+
*/
|
|
123
|
+
ui?: XcUiPart[] | boolean;
|
|
80
124
|
/** 重资源加载策略, 见 XcHeavyMode。 */
|
|
81
125
|
heavy?: XcHeavyMode;
|
|
126
|
+
/**
|
|
127
|
+
* 允许 xc.setModel 加载任意 https URL 的 .vrm/.vrmaddon/.vrmbase。默认 false (关闭): 只能用 capabilities.outfits 里的内置服装。
|
|
128
|
+
* 由宿主 SDK 的 `allowCustomModel` 选项发出 (放在 xc.init 的 config 里)。
|
|
129
|
+
*/
|
|
130
|
+
allowCustomModel?: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* 手势开关 (宿主 SDK 的 `draggable` / `resizable` 选项发出; 放在 xc.init 的 config 里, 也可用 xc.setConfig 运行时改)。
|
|
133
|
+
* 默认全 false: iframe 里**不识别**对应手势、也不拦截任何指针事件。仅当 capabilities.gestures 存在时才有意义 (旧版 /embed 忽略)。
|
|
134
|
+
* move = 左键在角色上拖动 → 发 xc.gesture-move, 由宿主移动 iframe
|
|
135
|
+
* resize = 在 iframe 四角热区按下拖动 → 发 xc.gesture-resize, 由宿主缩放 iframe
|
|
136
|
+
*/
|
|
137
|
+
gestures?: {
|
|
138
|
+
move?: boolean;
|
|
139
|
+
resize?: boolean;
|
|
140
|
+
};
|
|
82
141
|
}
|
|
83
142
|
export interface XcInitPayload {
|
|
84
143
|
/** 宿主声明的自身 origin (iframe 会与 event.origin 交叉校验)。 */
|
|
@@ -163,12 +222,37 @@ export interface XcPointerPayload {
|
|
|
163
222
|
y: number;
|
|
164
223
|
}
|
|
165
224
|
export interface XcSetModelPayload {
|
|
166
|
-
/** 完整 .vrm / .vrmaddon / .vrmbase URL (需 CORS)。 */
|
|
225
|
+
/** 完整 .vrm / .vrmaddon / .vrmbase URL (需 CORS)。**默认关闭**, 需要 xc.init config.allowCustomModel=true, 否则回 unsupported。 */
|
|
167
226
|
url?: string;
|
|
168
|
-
/** 内置服装
|
|
227
|
+
/** 内置服装 id (capabilities.outfits 里的 id, 如 'xiaochun_maid')。裸模 'base' 不对外开放 (回 unknown_id)。新代码请用 xc.setOutfit。 */
|
|
169
228
|
outfit?: string;
|
|
170
229
|
name?: string;
|
|
171
230
|
}
|
|
231
|
+
/** xc.setOutfit — 换内置服装 (id 来自 xc.ready.capabilities.outfits)。串行 + last-wins, 见 docs/EMBED.md。 */
|
|
232
|
+
export interface XcSetOutfitPayload {
|
|
233
|
+
id: string;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* xc.prefetch — 预取服装资源到 iframe 的 IndexedDB (之后 xc.setOutfit 直接命中缓存, 不走网络)。默认不会自动发生。
|
|
237
|
+
* 全局串行 (并发 1); 开了 heavy:'eager' 时排在 EMAGE 加载之后, 也不会和进行中的换装抢带宽。
|
|
238
|
+
* ids 省略 = 全部内置服装, **除了**体积大的婚纱 (xiaochun_wedding, 13.9MB); 显式点名则照做。
|
|
239
|
+
* 应答 xc.prefetched (带同一个信封 id)。
|
|
240
|
+
*/
|
|
241
|
+
export interface XcPrefetchPayload {
|
|
242
|
+
ids?: string[];
|
|
243
|
+
}
|
|
244
|
+
/** xc.prefetched — 预取完成 (逐项成败不影响整体; failed 里的 id 可稍后重试)。 */
|
|
245
|
+
export interface XcPrefetchedPayload {
|
|
246
|
+
downloaded: string[];
|
|
247
|
+
cached: string[];
|
|
248
|
+
failed: string[];
|
|
249
|
+
/** 'save-data' = 用户开了省流量模式, 整个请求被跳过。 */
|
|
250
|
+
skipped?: 'save-data';
|
|
251
|
+
}
|
|
252
|
+
/** xc.setScene — 运行时切场景 (id 来自 xc.ready.capabilities.scenes: light / dark / transparent)。 */
|
|
253
|
+
export interface XcSetScenePayload {
|
|
254
|
+
id: string;
|
|
255
|
+
}
|
|
172
256
|
export interface XcMicPayload {
|
|
173
257
|
enabled: boolean;
|
|
174
258
|
}
|
|
@@ -188,16 +272,68 @@ export interface XcReadyPayload {
|
|
|
188
272
|
};
|
|
189
273
|
/** iframe 内 `self.crossOriginIsolated` (true = 可用 SharedArrayBuffer / 多线程 wasm)。旧版 /embed 没有这个字段。仅诊断用。 */
|
|
190
274
|
crossOriginIsolated?: boolean;
|
|
275
|
+
/** 可换的内置服装 (不含裸模)。旧版 /embed 没有这个字段 → 宿主应视为"不支持 xc.setOutfit"。 */
|
|
276
|
+
outfits?: XcOutfitInfo[];
|
|
277
|
+
/** 可切的场景。旧版 /embed 没有这个字段 → 视为"不支持 xc.setScene"。 */
|
|
278
|
+
scenes?: XcSceneInfo[];
|
|
279
|
+
/** 支持 xc.prefetch。旧版 /embed 没有这个字段。 */
|
|
280
|
+
prefetch?: boolean;
|
|
281
|
+
/**
|
|
282
|
+
* iframe 内手势 (拖动 / 角落缩放) 支持情况; 旧版 /embed 没有这个字段 → 宿主视为"不支持", draggable / resizable 无效。
|
|
283
|
+
* cornerSize = 四角热区边长 (CSS px, iframe 视口左上/右上/左下/右下各一个正方形), 宿主 UI 可据此提示。
|
|
284
|
+
*/
|
|
285
|
+
gestures?: {
|
|
286
|
+
move: boolean;
|
|
287
|
+
resize: boolean;
|
|
288
|
+
cornerSize: number;
|
|
289
|
+
};
|
|
191
290
|
};
|
|
192
291
|
}
|
|
292
|
+
export interface XcOutfitInfo {
|
|
293
|
+
id: string;
|
|
294
|
+
/** 展示名 (英文, 来自主仓库配置)。 */
|
|
295
|
+
name: string;
|
|
296
|
+
/** 服装文件大小提示 (MB, 十进制, 约值; 公共的裸模 ~5.7MB 不计)。仅供宿主自己的 UI 参考 (内置换装菜单不显示体积); 旧版 /embed 没有这个字段。 */
|
|
297
|
+
sizeMB?: number;
|
|
298
|
+
}
|
|
299
|
+
export interface XcSceneInfo {
|
|
300
|
+
id: string;
|
|
301
|
+
/** true = 背景透明 (叠在宿主页上, 可开穿透)。 */
|
|
302
|
+
transparent: boolean;
|
|
303
|
+
}
|
|
193
304
|
export interface XcLoadProgressPayload {
|
|
194
|
-
|
|
305
|
+
/**
|
|
306
|
+
* 'model' = 首次加载; 'outfit' = 运行中换装 (id = 目标服装 id, 自定义 URL 时省略);
|
|
307
|
+
* 'prefetch' = 宿主发了 xc.prefetch 后的后台下载 (id = 正在预取的服装)。已有的"model 进度条"逻辑请先判断 phase。
|
|
308
|
+
*/
|
|
309
|
+
phase: 'model' | 'outfit' | 'prefetch';
|
|
195
310
|
/** 0~100 */
|
|
196
311
|
progress: number;
|
|
312
|
+
id?: string;
|
|
197
313
|
}
|
|
198
314
|
export interface XcLoadedPayload {
|
|
199
315
|
model: string;
|
|
200
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
* xc.outfit-changed — 服装已生效。三种来源: 首次加载 (initial:true)、xc.setOutfit / xc.setModel 的应答 (带同一个信封 id)。
|
|
319
|
+
* noop:true = 目标已经是当前服装 (或与另一个同目标请求合并), 没有重新加载; SDK 只用它 resolve 对应的 Promise, 不再触发事件。
|
|
320
|
+
*/
|
|
321
|
+
export interface XcOutfitChangedPayload {
|
|
322
|
+
/** 内置服装 id; 自定义 URL 模型为 null。 */
|
|
323
|
+
id: string | null;
|
|
324
|
+
name: string;
|
|
325
|
+
previous?: string | null;
|
|
326
|
+
initial?: boolean;
|
|
327
|
+
noop?: boolean;
|
|
328
|
+
}
|
|
329
|
+
/** xc.scene-changed — 场景已生效 (首次握手后的当前场景 / xc.setScene 应答 / xc.setConfig{transparent} 引起的切换)。 */
|
|
330
|
+
export interface XcSceneChangedPayload {
|
|
331
|
+
id: string;
|
|
332
|
+
transparent: boolean;
|
|
333
|
+
previous?: string | null;
|
|
334
|
+
initial?: boolean;
|
|
335
|
+
noop?: boolean;
|
|
336
|
+
}
|
|
201
337
|
export interface XcStatePayload {
|
|
202
338
|
phase: XcPhase;
|
|
203
339
|
paused: boolean;
|
|
@@ -224,6 +360,31 @@ export interface XcHitRegionPayload {
|
|
|
224
360
|
x: number;
|
|
225
361
|
y: number;
|
|
226
362
|
}
|
|
363
|
+
/** 手势阶段: start = 按下后确认成手势; move = 增量; end = 结束 (松手 / 取消 / 失焦)。 */
|
|
364
|
+
export type XcGesturePhase = 'start' | 'move' | 'end';
|
|
365
|
+
/**
|
|
366
|
+
* xc.gesture-move / xc.gesture-resize 的公共字段。单位 CSS px (屏幕坐标差, iframe 被移动时仍稳定)。
|
|
367
|
+
* - gesture: 手势序号, 每次新手势 +1 (从 1 起); 宿主据此区分手势, 丢弃过期手势的迟到消息。
|
|
368
|
+
* - seq: 该手势内的消息序号, start = 0, 之后严格递增; 宿主丢弃 seq 不递增的消息。
|
|
369
|
+
* - dx / dy: 相对上一条消息的增量; totalDx / totalDy: 相对 start 的累计 (宿主以它为准计算, 增量只是参考, 丢包也不漂移)。
|
|
370
|
+
* - reason: 仅 phase='end' 带: up = 松手, cancel = 指针被系统取消, blur = 窗口失焦。
|
|
371
|
+
*/
|
|
372
|
+
export interface XcGestureBase {
|
|
373
|
+
gesture: number;
|
|
374
|
+
seq: number;
|
|
375
|
+
phase: XcGesturePhase;
|
|
376
|
+
dx: number;
|
|
377
|
+
dy: number;
|
|
378
|
+
totalDx: number;
|
|
379
|
+
totalDy: number;
|
|
380
|
+
reason?: 'up' | 'cancel' | 'blur';
|
|
381
|
+
}
|
|
382
|
+
export type XcGestureMovePayload = XcGestureBase;
|
|
383
|
+
export type XcGestureCorner = 'NW' | 'NE' | 'SW' | 'SE';
|
|
384
|
+
export interface XcGestureResizePayload extends XcGestureBase {
|
|
385
|
+
/** 被拖的角; 对角固定不动。 */
|
|
386
|
+
corner: XcGestureCorner;
|
|
387
|
+
}
|
|
227
388
|
export interface XcErrorPayload {
|
|
228
389
|
code: XcErrorCode;
|
|
229
390
|
message: string;
|
|
@@ -241,6 +402,9 @@ export interface XcHostPayloadMap {
|
|
|
241
402
|
'xc.lookAt': XcLookAtPayload;
|
|
242
403
|
'xc.pointer': XcPointerPayload;
|
|
243
404
|
'xc.setModel': XcSetModelPayload;
|
|
405
|
+
'xc.setOutfit': XcSetOutfitPayload;
|
|
406
|
+
'xc.setScene': XcSetScenePayload;
|
|
407
|
+
'xc.prefetch': XcPrefetchPayload;
|
|
244
408
|
'xc.setConfig': XcConfig;
|
|
245
409
|
'xc.mic': XcMicPayload;
|
|
246
410
|
'xc.pause': undefined;
|
|
@@ -251,10 +415,15 @@ export interface XcFramePayloadMap {
|
|
|
251
415
|
'xc.ready': XcReadyPayload;
|
|
252
416
|
'xc.load.progress': XcLoadProgressPayload;
|
|
253
417
|
'xc.loaded': XcLoadedPayload;
|
|
418
|
+
'xc.outfit-changed': XcOutfitChangedPayload;
|
|
419
|
+
'xc.scene-changed': XcSceneChangedPayload;
|
|
420
|
+
'xc.prefetched': XcPrefetchedPayload;
|
|
254
421
|
'xc.state': XcStatePayload;
|
|
255
422
|
'xc.stt': XcSttPayload;
|
|
256
423
|
'xc.utterance': XcUtterancePayload;
|
|
257
424
|
'xc.hit-region': XcHitRegionPayload;
|
|
425
|
+
'xc.gesture-move': XcGestureMovePayload;
|
|
426
|
+
'xc.gesture-resize': XcGestureResizePayload;
|
|
258
427
|
'xc.error': XcErrorPayload;
|
|
259
428
|
}
|
|
260
429
|
export type XcHostMessage = {
|
package/dist/protocol.js
CHANGED
|
@@ -1,28 +1,46 @@
|
|
|
1
|
-
/*! @firetable/project-xiaochun v0.1.
|
|
1
|
+
/*! @firetable/project-xiaochun v0.1.15 | MIT | https://github.com/FireTable/project-xiaochun */
|
|
2
2
|
import {
|
|
3
3
|
XC_DEFAULT_ORIGIN,
|
|
4
4
|
XC_DEFAULT_SRC,
|
|
5
5
|
XC_FRAME_TO_HOST,
|
|
6
6
|
XC_HOST_TO_FRAME,
|
|
7
|
+
XC_ID_RE,
|
|
7
8
|
XC_IMPLEMENTED_COMMANDS,
|
|
8
9
|
XC_PROTOCOL_MAPPING,
|
|
9
10
|
XC_PROTOCOL_VERSION,
|
|
11
|
+
XC_UI_LEGACY_PARTS,
|
|
12
|
+
XC_UI_PARTS,
|
|
10
13
|
XC_UNSUPPORTED_COMMANDS,
|
|
14
|
+
XC_WINDOW_CORNER_RADIUS,
|
|
11
15
|
isXcEnvelope,
|
|
16
|
+
isXcId,
|
|
12
17
|
normalizeOrigin,
|
|
18
|
+
normalizeXcUiOption,
|
|
19
|
+
parseXcUiList,
|
|
20
|
+
parseXcUiParam,
|
|
21
|
+
xcHasOwn,
|
|
13
22
|
xcMessage
|
|
14
|
-
} from "./chunks/chunk-
|
|
23
|
+
} from "./chunks/chunk-KKRZ3SUD.js";
|
|
15
24
|
export {
|
|
16
25
|
XC_DEFAULT_ORIGIN,
|
|
17
26
|
XC_DEFAULT_SRC,
|
|
18
27
|
XC_FRAME_TO_HOST,
|
|
19
28
|
XC_HOST_TO_FRAME,
|
|
29
|
+
XC_ID_RE,
|
|
20
30
|
XC_IMPLEMENTED_COMMANDS,
|
|
21
31
|
XC_PROTOCOL_MAPPING,
|
|
22
32
|
XC_PROTOCOL_VERSION,
|
|
33
|
+
XC_UI_LEGACY_PARTS,
|
|
34
|
+
XC_UI_PARTS,
|
|
23
35
|
XC_UNSUPPORTED_COMMANDS,
|
|
36
|
+
XC_WINDOW_CORNER_RADIUS,
|
|
24
37
|
isXcEnvelope,
|
|
38
|
+
isXcId,
|
|
25
39
|
normalizeOrigin,
|
|
40
|
+
normalizeXcUiOption,
|
|
41
|
+
parseXcUiList,
|
|
42
|
+
parseXcUiParam,
|
|
43
|
+
xcHasOwn,
|
|
26
44
|
xcMessage
|
|
27
45
|
};
|
|
28
46
|
//# sourceMappingURL=protocol.js.map
|