@firetable/project-xiaochun 0.1.15 → 0.1.16
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 +24 -14
- package/README.md +24 -14
- package/dist/avatar-element.d.ts +10 -4
- package/dist/chunks/{chunk-T6EHFEEZ.js → chunk-HZ2ADKG7.js} +39 -8
- package/dist/chunks/chunk-HZ2ADKG7.js.map +7 -0
- package/dist/chunks/{chunk-KKRZ3SUD.js → chunk-RHQW4QZH.js} +86 -3
- package/dist/chunks/chunk-RHQW4QZH.js.map +7 -0
- package/dist/chunks/{chunk-KQSKHPVF.js → chunk-UWL65W7D.js} +136 -10
- package/dist/chunks/chunk-UWL65W7D.js.map +7 -0
- package/dist/client.d.ts +64 -5
- package/dist/element.cjs +206 -12
- package/dist/element.cjs.map +2 -2
- package/dist/element.js +4 -4
- package/dist/index.cjs +247 -13
- package/dist/index.cjs.map +2 -2
- package/dist/index.js +20 -6
- package/dist/index.js.map +1 -1
- package/dist/loader.global.js +2 -2
- package/dist/loader.global.js.map +3 -3
- package/dist/protocol.cjs +86 -3
- package/dist/protocol.cjs.map +2 -2
- package/dist/protocol.d.ts +104 -3
- package/dist/protocol.js +18 -4
- package/dist/react.cjs +190 -14
- package/dist/react.cjs.map +2 -2
- package/dist/react.d.ts +6 -0
- package/dist/react.js +23 -9
- package/dist/react.js.map +2 -2
- package/package.json +2 -2
- package/dist/chunks/chunk-KKRZ3SUD.js.map +0 -7
- package/dist/chunks/chunk-KQSKHPVF.js.map +0 -7
- package/dist/chunks/chunk-T6EHFEEZ.js.map +0 -7
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @firetable/project-xiaochun v0.1.
|
|
1
|
+
/*! @firetable/project-xiaochun v0.1.16 | MIT | https://github.com/FireTable/project-xiaochun */
|
|
2
2
|
|
|
3
3
|
// src/protocol.ts
|
|
4
4
|
var XC_PROTOCOL_VERSION = 1;
|
|
@@ -30,6 +30,7 @@ var XC_FRAME_TO_HOST = [
|
|
|
30
30
|
"xc.loaded",
|
|
31
31
|
"xc.outfit-changed",
|
|
32
32
|
"xc.scene-changed",
|
|
33
|
+
"xc.lang-changed",
|
|
33
34
|
"xc.prefetched",
|
|
34
35
|
"xc.state",
|
|
35
36
|
"xc.stt",
|
|
@@ -66,7 +67,7 @@ var XC_PROTOCOL_MAPPING = [
|
|
|
66
67
|
{ xc: "xc.audio.chunk / xc.audio.end", note: "\u6D41\u5F0F PCM", action: "audio", url: null }
|
|
67
68
|
];
|
|
68
69
|
var XC_WINDOW_CORNER_RADIUS = 20;
|
|
69
|
-
var XC_UI_PARTS = ["chat", "bubble", "outfit", "scene"];
|
|
70
|
+
var XC_UI_PARTS = ["chat", "bubble", "outfit", "scene", "lang", "github"];
|
|
70
71
|
var XC_UI_PART_SET = new Set(XC_UI_PARTS);
|
|
71
72
|
var XC_UI_LEGACY_PARTS = ["chat", "bubble"];
|
|
72
73
|
function parseXcUiList(items) {
|
|
@@ -90,9 +91,84 @@ function normalizeXcUiOption(v) {
|
|
|
90
91
|
if (Array.isArray(v)) return parseXcUiList(v);
|
|
91
92
|
return { parts: [], unknown: [], legacy: false };
|
|
92
93
|
}
|
|
94
|
+
var XC_UI_AUTOHIDE_DEFAULT = "transparent";
|
|
95
|
+
function parseXcUiAutoHide(raw) {
|
|
96
|
+
if (raw === true || raw === false || raw === "transparent") return raw;
|
|
97
|
+
if (typeof raw !== "string") return void 0;
|
|
98
|
+
const v = raw.trim().toLowerCase();
|
|
99
|
+
if (v === "1" || v === "true") return true;
|
|
100
|
+
if (v === "0" || v === "false") return false;
|
|
101
|
+
if (v === "transparent") return "transparent";
|
|
102
|
+
return void 0;
|
|
103
|
+
}
|
|
104
|
+
function xcUiAutoHideActive(mode, sceneTransparent) {
|
|
105
|
+
return mode === "transparent" ? sceneTransparent : mode;
|
|
106
|
+
}
|
|
107
|
+
var XC_CAMERA_RANGES = {
|
|
108
|
+
fov: [15, 60],
|
|
109
|
+
distance: [1, 15],
|
|
110
|
+
height: [-1, 1]
|
|
111
|
+
};
|
|
112
|
+
var XC_CAMERA_NUM_KEYS = ["fov", "distance", "height"];
|
|
113
|
+
var XC_CAMERA_KEYS = ["fov", "distance", "height", "intro"];
|
|
114
|
+
function normalizeXcCamera(raw) {
|
|
115
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return { ok: false, error: "camera must be an object" };
|
|
116
|
+
const src = raw;
|
|
117
|
+
for (const k of Object.keys(src)) {
|
|
118
|
+
if (!XC_CAMERA_KEYS.includes(k)) return { ok: false, error: `unknown camera key: ${k} (expected ${XC_CAMERA_KEYS.join(" | ")})` };
|
|
119
|
+
}
|
|
120
|
+
const out = {};
|
|
121
|
+
const clamped = [];
|
|
122
|
+
for (const k of XC_CAMERA_NUM_KEYS) {
|
|
123
|
+
const v = src[k];
|
|
124
|
+
if (v === void 0) continue;
|
|
125
|
+
if (v === null) {
|
|
126
|
+
out[k] = null;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (typeof v !== "number" || !Number.isFinite(v)) return { ok: false, error: `camera.${k} must be a finite number or null` };
|
|
130
|
+
const [lo, hi] = XC_CAMERA_RANGES[k];
|
|
131
|
+
const c = Math.min(hi, Math.max(lo, v));
|
|
132
|
+
if (c !== v) clamped.push(k);
|
|
133
|
+
out[k] = c;
|
|
134
|
+
}
|
|
135
|
+
if (src.intro !== void 0) {
|
|
136
|
+
if (src.intro !== null && typeof src.intro !== "boolean") return { ok: false, error: "camera.intro must be a boolean or null" };
|
|
137
|
+
out.intro = src.intro;
|
|
138
|
+
}
|
|
139
|
+
return { ok: true, camera: out, clamped };
|
|
140
|
+
}
|
|
141
|
+
function parseXcCameraParams(get) {
|
|
142
|
+
const camera = {};
|
|
143
|
+
const warnings = [];
|
|
144
|
+
const names = { fov: "cameraFov", distance: "cameraDistance", height: "cameraHeight" };
|
|
145
|
+
for (const k of XC_CAMERA_NUM_KEYS) {
|
|
146
|
+
const raw = get(names[k]);
|
|
147
|
+
if (raw === null) continue;
|
|
148
|
+
const t = raw.trim();
|
|
149
|
+
const n = t === "" ? NaN : Number(t);
|
|
150
|
+
if (!Number.isFinite(n)) {
|
|
151
|
+
warnings.push(`ignoring invalid ?${names[k]}=${raw}`);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const [lo, hi] = XC_CAMERA_RANGES[k];
|
|
155
|
+
const c = Math.min(hi, Math.max(lo, n));
|
|
156
|
+
if (c !== n) warnings.push(`?${names[k]}=${raw} is out of range [${lo}, ${hi}], clamped to ${c}`);
|
|
157
|
+
camera[k] = c;
|
|
158
|
+
}
|
|
159
|
+
const intro = get("cameraIntro");
|
|
160
|
+
if (intro !== null) {
|
|
161
|
+
const v = intro.trim().toLowerCase();
|
|
162
|
+
if (v === "1" || v === "true") camera.intro = true;
|
|
163
|
+
else if (v === "0" || v === "false") camera.intro = false;
|
|
164
|
+
else warnings.push(`ignoring invalid ?cameraIntro=${intro}`);
|
|
165
|
+
}
|
|
166
|
+
return { camera, warnings };
|
|
167
|
+
}
|
|
93
168
|
var XC_ID_RE = /^[a-z][a-z0-9_]{0,63}$/;
|
|
94
169
|
var isXcId = (v) => typeof v === "string" && XC_ID_RE.test(v);
|
|
95
170
|
var xcHasOwn = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
|
|
171
|
+
var XC_LANGS = ["zh-CN", "en", "ja"];
|
|
96
172
|
function xcMessage(type, payload, id) {
|
|
97
173
|
const m = { type, v: XC_PROTOCOL_VERSION, payload };
|
|
98
174
|
if (id !== void 0) m.id = id;
|
|
@@ -129,11 +205,18 @@ export {
|
|
|
129
205
|
parseXcUiList,
|
|
130
206
|
parseXcUiParam,
|
|
131
207
|
normalizeXcUiOption,
|
|
208
|
+
XC_UI_AUTOHIDE_DEFAULT,
|
|
209
|
+
parseXcUiAutoHide,
|
|
210
|
+
xcUiAutoHideActive,
|
|
211
|
+
XC_CAMERA_RANGES,
|
|
212
|
+
normalizeXcCamera,
|
|
213
|
+
parseXcCameraParams,
|
|
132
214
|
XC_ID_RE,
|
|
133
215
|
isXcId,
|
|
134
216
|
xcHasOwn,
|
|
217
|
+
XC_LANGS,
|
|
135
218
|
xcMessage,
|
|
136
219
|
isXcEnvelope,
|
|
137
220
|
normalizeOrigin
|
|
138
221
|
};
|
|
139
|
-
//# sourceMappingURL=chunk-
|
|
222
|
+
//# sourceMappingURL=chunk-RHQW4QZH.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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.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.lang-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,lang,github` / 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 * lang \u8BED\u8A00\u5207\u6362\u6309\u94AE (\u4E2D\u6587 / English / \u65E5\u672C\u8A9E; \u4E0E\u4E3B\u7AD9 TopHeader \u540C\u4E00\u4E2A\u7EC4\u4EF6)\n * github GitHub \u94FE\u63A5\u6309\u94AE (\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u9879\u76EE\u4ED3\u5E93; \u4E0E\u4E3B\u7AD9 TopHeader \u540C\u4E00\u4E2A\u7EC4\u4EF6)\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', 'lang', 'github'] 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\n/**\n * \u5185\u7F6E\u754C\u9762 (\u9876\u680F\u6309\u94AE outfit/scene/lang/github + \u804A\u5929\u680F chat) \u7684\u663E\u793A\u7B56\u7565, \u4E0E Tauri \u684C\u5BA0\u7684 \"\u70B9\u51FB\u89D2\u8272\u624D\u51FA\u73B0\" \u540C\u4E00\u5957\u72B6\u6001\u673A (src/hooks/usePetUiVisibility.ts):\n * 'transparent' (\u9ED8\u8BA4) \u4E0E Tauri \u5B8C\u5168\u4E00\u81F4: \u53EA\u6709\u900F\u660E\u573A\u666F\u9ED8\u8BA4\u9690\u85CF, \u5355\u51FB\u89D2\u8272\u51FA\u73B0 (\u6309\u4F4F\u62D6\u52A8 / \u957F\u6309\u62D6\u4E0D\u7B97), \u518D\u5355\u51FB\u89D2\u8272\u6536\u8D77, \u5355\u51FB\u7A7A\u767D\u6536\u8D77, 10 \u79D2\u65E0\u64CD\u4F5C\u81EA\u52A8\u6536\u8D77\n * (\u60AC\u505C\u5728\u6309\u94AE / \u804A\u5929\u680F\u4E0A\u3001\u83DC\u5355\u6216\u5BF9\u8BDD\u6846\u6253\u5F00\u3001\u8F93\u5165\u6846\u805A\u7126 / \u6709\u8F93\u5165 / \u53D1\u9001\u4E2D \u65F6\u4E0D\u6536); \u4EAE / \u6697\u573A\u666F\u5E38\u663E\u3002\n * true \u6240\u6709\u573A\u666F\u90FD\u6309\u4E0A\u9762\u7684\u70B9\u51FB\u51FA\u73B0\u89C4\u5219 (\u4EAE / \u6697\u573A\u666F\u4E5F\u9ED8\u8BA4\u9690\u85CF)\u3002\n * false \u4E00\u76F4\u663E\u793A (\u5FFD\u7565\u70B9\u51FB\u89C4\u5219)\u3002\n */\nexport type XcUiAutoHide = boolean | 'transparent';\n/** \u9ED8\u8BA4\u503C: 'transparent' (\u4E0E Tauri \u4E00\u81F4: \u53EA\u6709\u900F\u660E\u573A\u666F\u70B9\u51FB\u51FA\u73B0)\u3002 */\nexport const XC_UI_AUTOHIDE_DEFAULT: XcUiAutoHide = 'transparent';\n/** `?uiAutoHide=` \u53C2\u6570 / \u5143\u7D20\u5C5E\u6027: 1|true \u2192 true; 0|false \u2192 false; transparent \u2192 'transparent'; \u7F3A\u7701 / \u975E\u6CD5 \u2192 undefined (\u8C03\u7528\u65B9\u7528\u9ED8\u8BA4\u503C)\u3002 */\nexport function parseXcUiAutoHide(raw: unknown): XcUiAutoHide | undefined {\n if (raw === true || raw === false || raw === 'transparent') return raw;\n if (typeof raw !== 'string') return undefined;\n const v = raw.trim().toLowerCase();\n if (v === '1' || v === 'true') return true;\n if (v === '0' || v === 'false') return false;\n if (v === 'transparent') return 'transparent';\n return undefined;\n}\n/** \u67D0\u4E2A uiAutoHide \u53D6\u503C\u5728\u5F53\u524D\u573A\u666F\u4E0B\u662F\u5426\u751F\u6548 (\u9690\u85CF + \u70B9\u51FB\u51FA\u73B0)\u3002 */\nexport function xcUiAutoHideActive(mode: XcUiAutoHide, sceneTransparent: boolean): boolean {\n return mode === 'transparent' ? sceneTransparent : mode;\n}\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u76F8\u673A (camera) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/**\n * \u5BBF\u4E3B\u53EF\u8C03\u7684\u76F8\u673A\u9009\u9879 (SDK `camera` \u9009\u9879 / `?cameraFov=` \u7B49 URL \u53C2\u6570 / `xc.setConfig{camera}`)\u3002\n * fov \u89C6\u91CE\u89D2 (\u5EA6, \u9ED8\u8BA4 30): \u8D8A\u5C0F\u8D8A\u50CF\u957F\u7126 (\u900F\u89C6\u538B\u7F29\u3001\u80CC\u666F\u865A), \u8D8A\u5927\u8D8A\u5E7F\u89D2\u3002\u4E3B\u4F53\u53D6\u666F\u5927\u5C0F\u4F1A\u968F fov \u81EA\u52A8\u8865\u507F (\u8DDD\u79BB = \u53D6\u666F\u8303\u56F4 / (2\u00B7tan(fov/2))), \u6240\u4EE5\u53EA\u6539 fov \u65F6\u89D2\u8272\u5728\u753B\u9762\u91CC\u7684\u5927\u5C0F\u57FA\u672C\u4E0D\u53D8\n * distance \u76F8\u673A\u5230\u89D2\u8272\u7684\u89C6\u8DDD (\u7C73, \u9ED8\u8BA4\u7EA6 2.5): \u8D8A\u5C0F\u89D2\u8272\u8D8A\u5927\u3002\u663E\u5F0F\u8BBE\u7F6E\u540E, \u9996\u6B21\u53D6\u666F\u65F6\u5FFD\u7565 iframe \u91CC\u4FDD\u5B58\u7684\u89C6\u8DDD / \u4FEF\u4EF0 (\u7528\u6237\u4E4B\u540E\u6EDA\u8F6E\u7F29\u653E\u4ECD\u53EF\u8C03, \u4E14\u6362\u88C5 / \u6362\u573A\u666F\u4E0D\u4F1A\u88AB\u62FD\u56DE)\n * height \u53D6\u666F\u9AD8\u5EA6\u504F\u79FB (\u7C73, \u9ED8\u8BA4 0): \u6B63\u503C = \u76F8\u673A\u8FDE\u540C\u76EE\u6807\u4E00\u8D77\u4E0A\u62AC (\u89D2\u8272\u5728\u753B\u9762\u91CC\u4E0B\u79FB), \u8D1F\u503C\u76F8\u53CD\u3002\u663E\u5F0F\u503C\u4E0D\u5199\u5165 iframe \u7684\u4FDD\u5B58\u503C\n * intro \u662F\u5426\u64AD\u653E\u52A0\u8F7D\u5B8C\u6210\u540E\u7684\u63A8\u955C\u5934\u52A8\u753B (\u9ED8\u8BA4 true); false = \u76F4\u63A5\u653E\u5230\u7EC8\u70B9\n * \u7F3A\u7701 / undefined = \u4E0D\u8986\u76D6 (\u8D70 iframe \u4FDD\u5B58\u503C / \u9ED8\u8BA4\u503C); `xc.setConfig` \u91CC null = \u6E05\u9664\u8BE5\u9879\u8986\u76D6\u3002\n */\nexport interface XcCamera {\n fov?: number | null;\n distance?: number | null;\n height?: number | null;\n intro?: boolean | null;\n}\n/**\n * \u5404\u6570\u503C\u9879\u5141\u8BB8\u7684\u8303\u56F4 (\u8D8A\u754C\u503C\u4F1A\u88AB\u5939\u5230\u8FB9\u754C)\u3002\u4E0E\u4E3B\u4ED3\u5E93 APP_CONFIG.camera (minFov/maxFov\u3001defaultMin/MaxDistance\u3001hostMaxYOffset)\n * \u4E00\u81F4, \u4E3B\u4ED3\u5E93\u6709\u5355\u6D4B\u6838\u5BF9\u3002capabilities.camera \u4E5F\u4F1A\u4E0A\u62A5\u8FD9\u4EFD\u8303\u56F4\u3002\n */\nexport const XC_CAMERA_RANGES = {\n fov: [15, 60],\n distance: [1, 15],\n height: [-1, 1],\n} as const;\nconst XC_CAMERA_NUM_KEYS = ['fov', 'distance', 'height'] as const;\nconst XC_CAMERA_KEYS = ['fov', 'distance', 'height', 'intro'] as const;\n\nexport type XcCameraNormalized =\n | { ok: true; camera: XcCamera; /** \u88AB\u5939\u5230\u8303\u56F4\u8FB9\u754C\u7684\u9879 (\u4F9B\u544A\u8B66) */ clamped: string[] }\n | { ok: false; error: string };\n\n/**\n * \u6821\u9A8C + \u89C4\u6574\u4E00\u4E2A camera \u5BF9\u8C61 (xc.setConfig / SDK \u9009\u9879\u5171\u7528)\u3002\n * \u6570\u5B57\u9879\u5FC5\u987B\u662F\u6709\u9650 number (\u8D8A\u754C\u5939\u5230\u8303\u56F4\u5185); intro \u5FC5\u987B\u662F boolean; null = \u6E05\u9664; \u672A\u77E5\u952E / \u975E\u5BF9\u8C61 \u2192 \u9519\u8BEF\u3002\n */\nexport function normalizeXcCamera(raw: unknown): XcCameraNormalized {\n if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return { ok: false, error: 'camera must be an object' };\n const src = raw as Record<string, unknown>;\n for (const k of Object.keys(src)) {\n if (!(XC_CAMERA_KEYS as readonly string[]).includes(k)) return { ok: false, error: `unknown camera key: ${k} (expected ${XC_CAMERA_KEYS.join(' | ')})` };\n }\n const out: XcCamera = {};\n const clamped: string[] = [];\n for (const k of XC_CAMERA_NUM_KEYS) {\n const v = src[k];\n if (v === undefined) continue;\n if (v === null) { out[k] = null; continue; }\n if (typeof v !== 'number' || !Number.isFinite(v)) return { ok: false, error: `camera.${k} must be a finite number or null` };\n const [lo, hi] = XC_CAMERA_RANGES[k];\n const c = Math.min(hi, Math.max(lo, v));\n if (c !== v) clamped.push(k);\n out[k] = c;\n }\n if (src.intro !== undefined) {\n if (src.intro !== null && typeof src.intro !== 'boolean') return { ok: false, error: 'camera.intro must be a boolean or null' };\n out.intro = src.intro;\n }\n return { ok: true, camera: out, clamped };\n}\n\n/**\n * \u89E3\u6790 URL \u53C2\u6570 cameraFov / cameraDistance / cameraHeight / cameraIntro (SDK \u751F\u6210 / \u624B\u5199 iframe \u5747\u53EF)\u3002\n * \u6570\u503C\u975E\u6CD5 \u2192 \u5FFD\u7565\u5E76\u8BB0 warning; \u8D8A\u754C \u2192 \u5939\u5230\u8303\u56F4\u5E76\u8BB0 warning; cameraIntro \u8BA4 0|1|true|false\u3002\n */\nexport function parseXcCameraParams(get: (name: string) => string | null): { camera: XcCamera; warnings: string[] } {\n const camera: XcCamera = {};\n const warnings: string[] = [];\n const names = { fov: 'cameraFov', distance: 'cameraDistance', height: 'cameraHeight' } as const;\n for (const k of XC_CAMERA_NUM_KEYS) {\n const raw = get(names[k]);\n if (raw === null) continue;\n const t = raw.trim();\n const n = t === '' ? NaN : Number(t);\n if (!Number.isFinite(n)) { warnings.push(`ignoring invalid ?${names[k]}=${raw}`); continue; }\n const [lo, hi] = XC_CAMERA_RANGES[k];\n const c = Math.min(hi, Math.max(lo, n));\n if (c !== n) warnings.push(`?${names[k]}=${raw} is out of range [${lo}, ${hi}], clamped to ${c}`);\n camera[k] = c;\n }\n const intro = get('cameraIntro');\n if (intro !== null) {\n const v = intro.trim().toLowerCase();\n if (v === '1' || v === 'true') camera.intro = true;\n else if (v === '0' || v === 'false') camera.intro = false;\n else warnings.push(`ignoring invalid ?cameraIntro=${intro}`);\n }\n return { camera, warnings };\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';\n/** iframe \u652F\u6301\u7684\u754C\u9762\u8BED\u8A00 (\u4E0E\u4E3B\u7AD9 SUPPORTED_LANGS \u540C\u4E00\u4EFD; capabilities.ui.langs)\u3002 */\nexport const XC_LANGS: readonly 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 /**\n * \u5185\u7F6E\u754C\u9762\u7684\u663E\u793A\u7B56\u7565 (\u89C1 XcUiAutoHide): \u9ED8\u8BA4 'transparent' = \u4E0E Tauri \u4E00\u81F4 (\u53EA\u5728\u900F\u660E\u573A\u666F\u9690\u85CF\u3001\u5355\u51FB\u89D2\u8272\u51FA\u73B0, \u4EAE / \u6697\u573A\u666F\u5E38\u663E); true = \u6240\u6709\u573A\u666F\u90FD\u70B9\u51FB\u51FA\u73B0; false = \u4E00\u76F4\u663E\u793A\u3002\n * \u4EC5\u5F53 capabilities.ui \u5B58\u5728\u65F6\u624D\u6709\u610F\u4E49 (\u65E7\u7248 /embed \u5FFD\u7565, \u5E38\u663E)\u3002\n */\n uiAutoHide?: XcUiAutoHide;\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 * \u76F8\u673A\u9009\u9879 (\u89C1 XcCamera); \u653E\u5728 xc.init \u7684 config \u91CC\u6216\u7528 xc.setConfig \u8FD0\u884C\u65F6\u6539 (\u7F3A\u7701\u952E = \u4E0D\u53D8, null = \u6062\u590D\u9ED8\u8BA4, \u975E\u6CD5\u503C \u2192 xc.error{bad_request})\u3002\n * \u8FD0\u884C\u65F6\u6539\u4F1A\u7ACB\u523B\u91CD\u65B0\u53D6\u666F\u5E76\u53D6\u6D88\u8FDB\u884C\u4E2D\u7684\u63A8\u955C\u5934\u3002\u4EC5\u5F53 capabilities.camera \u5B58\u5728\u65F6\u624D\u6709\u610F\u4E49 (\u65E7\u7248 /embed \u5FFD\u7565)\u3002\n */\n camera?: XcCamera;\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 * \u5185\u7F6E\u754C\u9762\u80FD\u529B; \u65E7\u7248 /embed \u6CA1\u6709\u8FD9\u4E2A\u5B57\u6BB5 \u2192 \u5BBF\u4E3B\u89C6\u4E3A\"\u6CA1\u6709 lang / github \u90E8\u4EF6\u3001\u4E0D\u652F\u6301 uiAutoHide (\u754C\u9762\u5E38\u663E)\"\u3002\n * parts = \u53EF\u7528\u7684\u90E8\u4EF6\u540D; autoHide = \u652F\u6301 uiAutoHide (\u70B9\u51FB\u51FA\u73B0); langs = \u8BED\u8A00\u6309\u94AE / `lang` \u9009\u9879\u53EF\u7528\u7684\u53D6\u503C\u3002\n */\n ui?: { parts: XcUiPart[]; autoHide: boolean; langs: XcLang[] };\n /**\n * \u76F8\u673A\u9009\u9879\u80FD\u529B (\u89C1 XcCamera); \u65E7\u7248 /embed \u6CA1\u6709\u8FD9\u4E2A\u5B57\u6BB5 \u2192 \u5BBF\u4E3B\u89C6\u4E3A\"\u4E0D\u652F\u6301 camera \u9009\u9879\"\u3002\n * fov / distance / height = \u5404\u9879\u5141\u8BB8\u8303\u56F4 [min, max] (\u8D8A\u754C\u4F1A\u88AB\u5939\u5230\u8FB9\u754C); intro = \u652F\u6301\u5173\u95ED\u63A8\u955C\u5934\u3002\n */\n camera?: { fov: [number, number]; distance: [number, number]; height: [number, number]; intro: boolean };\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\n/**\n * xc.lang-changed \u2014 iframe \u5F53\u524D\u754C\u9762\u8BED\u8A00 (\u63E1\u624B\u540E\u4E0A\u62A5\u4E00\u6B21 initial:true; \u4E4B\u540E\u7528\u6237\u70B9\u8BED\u8A00\u6309\u94AE / xc.setConfig{lang} \u5F15\u8D77\u7684\u53D8\u5316)\u3002\n * \u521D\u59CB\u8BED\u8A00\u4F18\u5148\u7EA7: `?lang=` (SDK `lang` \u9009\u9879) > iframe \u81EA\u5DF1 localStorage \u91CC\u8BB0\u4F4F\u7684\u7528\u6237\u9009\u62E9 > \u6D4F\u89C8\u5668\u8BED\u8A00 > zh-CN\u3002\n */\nexport interface XcLangChangedPayload {\n lang: XcLang;\n previous?: XcLang | null;\n initial?: 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.lang-changed': XcLangChangedPayload;\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": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
/*! @firetable/project-xiaochun v0.1.
|
|
1
|
+
/*! @firetable/project-xiaochun v0.1.16 | MIT | https://github.com/FireTable/project-xiaochun */
|
|
2
2
|
import {
|
|
3
|
+
XC_CAMERA_RANGES,
|
|
3
4
|
XC_DEFAULT_SRC,
|
|
4
5
|
XC_UI_PARTS,
|
|
5
6
|
XC_WINDOW_CORNER_RADIUS,
|
|
6
7
|
isXcEnvelope,
|
|
7
8
|
isXcId,
|
|
8
9
|
normalizeOrigin,
|
|
10
|
+
normalizeXcCamera,
|
|
9
11
|
normalizeXcUiOption,
|
|
12
|
+
parseXcUiAutoHide,
|
|
10
13
|
xcMessage
|
|
11
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-RHQW4QZH.js";
|
|
12
15
|
|
|
13
16
|
// src/gesture-box.ts
|
|
14
17
|
var DEFAULT_RESIZE_LIMITS = Object.freeze({
|
|
@@ -103,7 +106,20 @@ var GestureGate = class {
|
|
|
103
106
|
var FALLBACK_PLACEHOLDER = "data:image/svg+xml;utf8," + encodeURIComponent(
|
|
104
107
|
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 180"><defs><linearGradient id="g" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f6b8ae"/><stop offset="1" stop-color="#ea8377"/></linearGradient></defs><ellipse cx="60" cy="170" rx="34" ry="6" fill="#000" opacity=".12"/><circle cx="60" cy="48" r="26" fill="url(#g)" opacity=".75"/><path d="M26 160c2-40 18-62 34-62s32 22 34 62z" fill="url(#g)" opacity=".6"/></svg>'
|
|
105
108
|
);
|
|
109
|
+
var noSelect = (st) => {
|
|
110
|
+
st.setProperty("user-select", "none");
|
|
111
|
+
st.setProperty("-webkit-user-select", "none");
|
|
112
|
+
st.setProperty("-webkit-touch-callout", "none");
|
|
113
|
+
st.setProperty("-webkit-tap-highlight-color", "transparent");
|
|
114
|
+
};
|
|
106
115
|
var css = (v, d) => v === void 0 ? d : typeof v === "number" ? `${v}px` : v;
|
|
116
|
+
var XC_DEFAULT_WIDTH = 600;
|
|
117
|
+
var XC_DEFAULT_HEIGHT = 1080;
|
|
118
|
+
var defaultWidthCss = (floating = false) => floating ? `min(${XC_DEFAULT_WIDTH}px, calc(100vw - 2 * var(--xc-offset-x, 16px)))` : `min(${XC_DEFAULT_WIDTH}px, 100vw)`;
|
|
119
|
+
var defaultHeightCss = (floating = false) => {
|
|
120
|
+
const unit = typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("height", "1svh") ? "svh" : "vh";
|
|
121
|
+
return floating ? `min(${XC_DEFAULT_HEIGHT}px, calc(100${unit} - 2 * var(--xc-offset-y, 16px)))` : `min(${XC_DEFAULT_HEIGHT}px, 100${unit})`;
|
|
122
|
+
};
|
|
107
123
|
function createXiaochun(options) {
|
|
108
124
|
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
109
125
|
throw new Error("[project-xiaochun] createXiaochun must run in a browser");
|
|
@@ -158,6 +174,16 @@ function createXiaochun(options) {
|
|
|
158
174
|
}
|
|
159
175
|
const uiParse = normalizeXcUiOption(options.ui);
|
|
160
176
|
const uiParts = uiParse.parts;
|
|
177
|
+
const uiAutoHide = parseXcUiAutoHide(options.uiAutoHide);
|
|
178
|
+
if (options.uiAutoHide !== void 0 && uiAutoHide === void 0) console.warn(`[project-xiaochun] ignoring invalid uiAutoHide: ${String(options.uiAutoHide)} (use true | false | 'transparent')`);
|
|
179
|
+
let camera = {};
|
|
180
|
+
if (options.camera !== void 0) {
|
|
181
|
+
const r = normalizeXcCamera(options.camera);
|
|
182
|
+
if (r.ok) {
|
|
183
|
+
camera = r.camera;
|
|
184
|
+
if (r.clamped.length) console.warn(`[project-xiaochun] camera.${r.clamped.join(", camera.")} out of range, clamped (fov ${XC_CAMERA_RANGES.fov.join("\u2013")}, distance ${XC_CAMERA_RANGES.distance.join("\u2013")}, height ${XC_CAMERA_RANGES.height.join("\u2013")})`);
|
|
185
|
+
} else console.warn(`[project-xiaochun] ignoring invalid camera option: ${r.error}`);
|
|
186
|
+
}
|
|
161
187
|
if (uiParse.legacy) console.warn(`[project-xiaochun] option "ui: true" is deprecated, use an array of parts, e.g. ui: ['chat', 'bubble', 'outfit', 'scene'].`);
|
|
162
188
|
if (uiParse.unknown.length) console.warn(`[project-xiaochun] ignoring unknown ui part(s): ${uiParse.unknown.join(", ")} (known: ${XC_UI_PARTS.join(", ")})`);
|
|
163
189
|
const outfitOpt = options.outfit ?? (isXcId(options.model) ? options.model : void 0);
|
|
@@ -172,11 +198,12 @@ function createXiaochun(options) {
|
|
|
172
198
|
wrapper.setAttribute("data-xiaochun", "");
|
|
173
199
|
const ws = wrapper.style;
|
|
174
200
|
ws.position = position === "inline" ? "relative" : "fixed";
|
|
175
|
-
ws.width = css(options.width, "
|
|
176
|
-
ws.height = css(options.height, "
|
|
201
|
+
ws.width = css(options.width, defaultWidthCss(position !== "inline"));
|
|
202
|
+
ws.height = css(options.height, defaultHeightCss(position !== "inline"));
|
|
177
203
|
ws.maxWidth = "100%";
|
|
178
204
|
ws.overflow = "visible";
|
|
179
205
|
ws.contain = "layout style";
|
|
206
|
+
noSelect(ws);
|
|
180
207
|
ws.boxShadow = "var(--xc-shadow, none)";
|
|
181
208
|
const applyWrapperBg = () => {
|
|
182
209
|
ws.background = transparent ? "" : "var(--xc-bg, transparent)";
|
|
@@ -211,8 +238,8 @@ function createXiaochun(options) {
|
|
|
211
238
|
img.src = options.placeholder || FALLBACK_PLACEHOLDER;
|
|
212
239
|
img.alt = "Project XiaoChun";
|
|
213
240
|
img.decoding = "async";
|
|
214
|
-
img.width =
|
|
215
|
-
img.height =
|
|
241
|
+
img.width = XC_DEFAULT_WIDTH;
|
|
242
|
+
img.height = XC_DEFAULT_HEIGHT;
|
|
216
243
|
placeholderEl = img;
|
|
217
244
|
}
|
|
218
245
|
const ps = placeholderEl.style;
|
|
@@ -226,6 +253,7 @@ function createXiaochun(options) {
|
|
|
226
253
|
ps.transition = "opacity .25s ease";
|
|
227
254
|
ps.pointerEvents = "auto";
|
|
228
255
|
ps.cursor = lazy === false ? "default" : "pointer";
|
|
256
|
+
noSelect(ps);
|
|
229
257
|
placeholderEl.addEventListener("click", () => activate());
|
|
230
258
|
wrapper.appendChild(placeholderEl);
|
|
231
259
|
}
|
|
@@ -384,6 +412,11 @@ function createXiaochun(options) {
|
|
|
384
412
|
if (transparent) u.searchParams.set("transparent", "1");
|
|
385
413
|
if (uiParts.length) u.searchParams.set("ui", uiParts.join(","));
|
|
386
414
|
if (options.lang) u.searchParams.set("lang", options.lang);
|
|
415
|
+
if (uiAutoHide !== void 0) u.searchParams.set("uiAutoHide", String(uiAutoHide));
|
|
416
|
+
if (typeof camera.fov === "number") u.searchParams.set("cameraFov", String(camera.fov));
|
|
417
|
+
if (typeof camera.distance === "number") u.searchParams.set("cameraDistance", String(camera.distance));
|
|
418
|
+
if (typeof camera.height === "number") u.searchParams.set("cameraHeight", String(camera.height));
|
|
419
|
+
if (typeof camera.intro === "boolean") u.searchParams.set("cameraIntro", camera.intro ? "1" : "0");
|
|
387
420
|
if (options.heavy) u.searchParams.set("heavy", options.heavy);
|
|
388
421
|
if (options.controls === false) u.searchParams.set("controls", "0");
|
|
389
422
|
if (initialScene && isXcId(initialScene)) u.searchParams.set("scene", initialScene);
|
|
@@ -415,6 +448,7 @@ function createXiaochun(options) {
|
|
|
415
448
|
s.colorScheme = "normal";
|
|
416
449
|
s.borderRadius = `var(--xc-radius, ${radiusValue()})`;
|
|
417
450
|
f.setAttribute("part", "iframe");
|
|
451
|
+
noSelect(s);
|
|
418
452
|
s.opacity = "0";
|
|
419
453
|
s.transition = "opacity .25s ease";
|
|
420
454
|
s.pointerEvents = passthrough ? "none" : "auto";
|
|
@@ -520,6 +554,9 @@ function createXiaochun(options) {
|
|
|
520
554
|
ack(data.id);
|
|
521
555
|
break;
|
|
522
556
|
}
|
|
557
|
+
case "xc.lang-changed":
|
|
558
|
+
emit("lang-changed", data.payload);
|
|
559
|
+
break;
|
|
523
560
|
case "xc.prefetched":
|
|
524
561
|
ack(data.id, data.payload);
|
|
525
562
|
break;
|
|
@@ -764,6 +801,7 @@ function createXiaochun(options) {
|
|
|
764
801
|
emit(activeGesture.kind, { phase: "end", ...b });
|
|
765
802
|
activeGesture = null;
|
|
766
803
|
lockSelection(false);
|
|
804
|
+
saveBox();
|
|
767
805
|
}
|
|
768
806
|
function onGesture(kind, raw) {
|
|
769
807
|
if (destroyed || !caps?.gestures) return;
|
|
@@ -792,6 +830,7 @@ function createXiaochun(options) {
|
|
|
792
830
|
if (p.phase === "end") {
|
|
793
831
|
activeGesture = null;
|
|
794
832
|
lockSelection(false);
|
|
833
|
+
saveBox();
|
|
795
834
|
}
|
|
796
835
|
}
|
|
797
836
|
function syncGestures() {
|
|
@@ -816,11 +855,63 @@ function createXiaochun(options) {
|
|
|
816
855
|
const onWindowResize = () => {
|
|
817
856
|
if (!userMoved || activeGesture || destroyed) return;
|
|
818
857
|
const cur = readBox();
|
|
819
|
-
const
|
|
858
|
+
const vp = viewport();
|
|
859
|
+
const fit = position === "inline" ? { ...fitBox({ ...cur, top: 0 }, { width: vp.width, height: Number.MAX_SAFE_INTEGER }), top: Math.max(cur.top, -window.scrollY) } : fitBox(cur, vp);
|
|
820
860
|
if (fit.left === cur.left && fit.top === cur.top && fit.width === cur.width && fit.height === cur.height) return;
|
|
821
861
|
applyBox(fit, position === "inline" ? { left: cur.left - inlineShift.x, top: cur.top - inlineShift.y } : null, fit.width !== cur.width || fit.height !== cur.height);
|
|
822
862
|
};
|
|
823
863
|
window.addEventListener("resize", onWindowResize, { passive: true });
|
|
864
|
+
const boxKey = options.persistBox === true ? "xiaochun:box" : typeof options.persistBox === "string" && options.persistBox ? `xiaochun:box:${options.persistBox}` : null;
|
|
865
|
+
const saveBox = () => {
|
|
866
|
+
if (!boxKey) return;
|
|
867
|
+
try {
|
|
868
|
+
const b = readBox();
|
|
869
|
+
const at = position === "inline" ? inlineShift : { x: b.left, y: b.top };
|
|
870
|
+
window.localStorage.setItem(boxKey, JSON.stringify({ v: 1, mode: position, x: Math.round(at.x), y: Math.round(at.y), width: Math.round(b.width), height: Math.round(b.height) }));
|
|
871
|
+
} catch {
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
const loadBox = () => {
|
|
875
|
+
if (!boxKey) return null;
|
|
876
|
+
try {
|
|
877
|
+
const raw = window.localStorage.getItem(boxKey);
|
|
878
|
+
if (raw === null) return null;
|
|
879
|
+
let o = null;
|
|
880
|
+
try {
|
|
881
|
+
o = JSON.parse(raw);
|
|
882
|
+
} catch {
|
|
883
|
+
}
|
|
884
|
+
const ok = (v, lo, hi) => typeof v === "number" && Number.isFinite(v) && v >= lo && v <= hi;
|
|
885
|
+
if (o && o.v === 1 && o.mode === position && ok(o.x, -1e5, 1e5) && ok(o.y, -1e5, 1e5) && ok(o.width, 1, 1e5) && ok(o.height, 1, 1e5)) {
|
|
886
|
+
return { x: o.x, y: o.y, width: o.width, height: o.height };
|
|
887
|
+
}
|
|
888
|
+
window.localStorage.removeItem(boxKey);
|
|
889
|
+
} catch {
|
|
890
|
+
}
|
|
891
|
+
return null;
|
|
892
|
+
};
|
|
893
|
+
function restoreBox() {
|
|
894
|
+
if (!boxKey || !draggableOn && resizeLimits === null) return;
|
|
895
|
+
const st = loadBox();
|
|
896
|
+
if (!st) return;
|
|
897
|
+
const vp = viewport();
|
|
898
|
+
const lim = resizeLimits ?? DEFAULT_RESIZE_LIMITS;
|
|
899
|
+
const cur = readBox();
|
|
900
|
+
const useW = resizeLimits !== null && options.width === void 0;
|
|
901
|
+
const useH = resizeLimits !== null && options.height === void 0;
|
|
902
|
+
const clampN = (v, lo, hi) => Math.min(Math.max(v, lo), Math.max(lo, hi));
|
|
903
|
+
const width = useW ? clampN(st.width, lim.minWidth, Math.min(lim.maxWidth, vp.width)) : cur.width;
|
|
904
|
+
const height = useH ? clampN(st.height, lim.minHeight, Math.min(lim.maxHeight, vp.height)) : cur.height;
|
|
905
|
+
if (position === "inline") {
|
|
906
|
+
const base = { left: cur.left - inlineShift.x, top: cur.top - inlineShift.y };
|
|
907
|
+
const left = clampN(base.left + st.x, 0, vp.width - width);
|
|
908
|
+
const top = Math.max(base.top + st.y, -window.scrollY);
|
|
909
|
+
applyBox({ left, top, width, height }, base, useW || useH);
|
|
910
|
+
} else {
|
|
911
|
+
applyBox(fitBox({ left: st.x, top: st.y, width, height }, vp), null, useW || useH);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
restoreBox();
|
|
824
915
|
scheduleLazy();
|
|
825
916
|
const api = {
|
|
826
917
|
element: wrapper,
|
|
@@ -877,11 +968,44 @@ function createXiaochun(options) {
|
|
|
877
968
|
setConfig: (cfg) => send("xc.setConfig", cfg),
|
|
878
969
|
setSize(width, height) {
|
|
879
970
|
if (destroyed) return;
|
|
880
|
-
ws.width = css(width, "
|
|
881
|
-
ws.height = css(height, "
|
|
971
|
+
ws.width = css(width, defaultWidthCss(position !== "inline"));
|
|
972
|
+
ws.height = css(height, defaultHeightCss(position !== "inline"));
|
|
973
|
+
if (ws.maxWidth === "none") ws.maxWidth = "100%";
|
|
882
974
|
invalidateRect();
|
|
883
975
|
},
|
|
884
976
|
getBox: () => readBox(),
|
|
977
|
+
clearPersistedBox(opts) {
|
|
978
|
+
if (boxKey) {
|
|
979
|
+
try {
|
|
980
|
+
window.localStorage.removeItem(boxKey);
|
|
981
|
+
} catch {
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
if (destroyed || opts?.reset !== true) return;
|
|
985
|
+
endActiveGesture();
|
|
986
|
+
ws.width = css(options.width, defaultWidthCss(position !== "inline"));
|
|
987
|
+
ws.height = css(options.height, defaultHeightCss(position !== "inline"));
|
|
988
|
+
ws.maxWidth = "100%";
|
|
989
|
+
if (position === "inline") {
|
|
990
|
+
inlineShift = { x: 0, y: 0 };
|
|
991
|
+
ws.removeProperty("translate");
|
|
992
|
+
} else {
|
|
993
|
+
ws.left = "";
|
|
994
|
+
ws.top = "";
|
|
995
|
+
ws.right = "";
|
|
996
|
+
ws.bottom = "";
|
|
997
|
+
if (position === "bottom-right") {
|
|
998
|
+
ws.right = "var(--xc-offset-x, 16px)";
|
|
999
|
+
ws.bottom = "var(--xc-offset-y, 16px)";
|
|
1000
|
+
}
|
|
1001
|
+
if (position === "bottom-left") {
|
|
1002
|
+
ws.left = "var(--xc-offset-x, 16px)";
|
|
1003
|
+
ws.bottom = "var(--xc-offset-y, 16px)";
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
userMoved = false;
|
|
1007
|
+
invalidateRect();
|
|
1008
|
+
},
|
|
885
1009
|
setBorderRadius(radius) {
|
|
886
1010
|
radiusOpt = radius;
|
|
887
1011
|
applyRadius();
|
|
@@ -952,6 +1076,8 @@ function createXiaochun(options) {
|
|
|
952
1076
|
}
|
|
953
1077
|
|
|
954
1078
|
export {
|
|
1079
|
+
defaultWidthCss,
|
|
1080
|
+
defaultHeightCss,
|
|
955
1081
|
createXiaochun
|
|
956
1082
|
};
|
|
957
|
-
//# sourceMappingURL=chunk-
|
|
1083
|
+
//# sourceMappingURL=chunk-UWL65W7D.js.map
|