@firetable/project-xiaochun 0.1.15 → 0.1.17

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.
@@ -1,4 +1,4 @@
1
- /*! @firetable/project-xiaochun v0.1.15 | MIT | https://github.com/FireTable/project-xiaochun */
1
+ /*! @firetable/project-xiaochun v0.1.17 | 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-KKRZ3SUD.js.map
222
+ //# sourceMappingURL=chunk-BJF55B44.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 * \u662F\u5426\u628A\u8FD9\u6BB5\u97F3\u9891\u9001\u5230\u626C\u58F0\u5668\u3002\u9ED8\u8BA4 true\u3002\n * false: \u4ECD\u89E3\u7801\u5E76\u751F\u6210\u52A8\u4F5C\u548C\u53E3\u578B, \u589E\u76CA\u4E3A 0\u3002\u5BBF\u4E3B\u9875\u9762\u81EA\u5DF1\u64AD\u653E\u540C\u4E00\u6BB5\u97F3\u9891\u65F6\u7528\u6765\u907F\u514D\u53E0\u58F0\u3002\n */\n audible?: 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\u7684\u65F6\u949F + \u53E3\u578B, \u64AD\u5B8C\u56DE xc.utterance{phase:'end'}\u3002\n * audible \u7F3A\u7701\u6216 true \u65F6\u8FD9\u6BB5\u97F3\u9891\u4E5F\u9001\u5230\u626C\u58F0\u5668; false \u65F6\u589E\u76CA\u4E3A 0, \u53EA\u7559\u52A8\u4F5C\u548C\u53E3\u578B\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
+ }
package/dist/client.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * 默认 lazy: 先放固定尺寸的占位 (不产生 CLS), 浏览器空闲 + 进入视口 + (或点击/调用 API) 之后才创建 iframe。
9
9
  */
10
- import { type XcAudioFormat, type XcAudioOptions, type XcConfig, type XcErrorCode, type XcExpressionPayload, type XcHeavyMode, type XcHitRegionPayload, type XcLang, type XcLoadProgressPayload, type XcMotionPayload, type XcOutfitChangedPayload, type XcOutfitInfo, type XcPrefetchedPayload, type XcReadyPayload, type XcSceneChangedPayload, type XcSceneId, type XcSceneInfo, type XcSttPayload, type XcStatePayload, type XcUiPart, type XcUtterancePayload } from './protocol';
10
+ import { type XcAudioFormat, type XcAudioOptions, type XcCamera, type XcConfig, type XcErrorCode, type XcExpressionPayload, type XcHeavyMode, type XcHitRegionPayload, type XcLang, type XcLangChangedPayload, type XcLoadProgressPayload, type XcMotionPayload, type XcOutfitChangedPayload, type XcOutfitInfo, type XcPrefetchedPayload, type XcReadyPayload, type XcSceneChangedPayload, type XcSceneId, type XcSceneInfo, type XcSttPayload, type XcStatePayload, type XcUiAutoHide, type XcUiPart, type XcUtterancePayload } from './protocol';
11
11
  import { type XiaochunResizeLimits } from './gesture-box';
12
12
  export type { XiaochunResizeLimits } from './gesture-box';
13
13
  export type XiaochunPosition = 'inline' | 'bottom-right' | 'bottom-left';
@@ -34,7 +34,11 @@ export interface XiaochunOptions {
34
34
  transparent?: boolean;
35
35
  /** 初始场景: 'light' | 'dark' | 'transparent' (以 getScenes() 为准)。省略 = 跟随 transparent 选项 / 系统亮暗。 */
36
36
  scene?: XcSceneId | (string & {});
37
- /** 固定尺寸, 数字=px, 字符串=CSS 长度。务必给定, 否则无法预留空间 (CLS)。默认 320x480。 */
37
+ /**
38
+ * 固定尺寸, 数字=px, 字符串=CSS 长度。默认 600x1080 (宽高比 ≈ 0.556, 与桌面端窗口的竖版比例同一量级)。
39
+ * **默认值受视口限制**: 宽 = min(600px, 视口宽) 且外壳 max-width:100% (不超过容器宽), 高 = min(1080px, 视口高), 窄屏 / 小窗口不会溢出; 显式给的值原样使用 (宽仍有 max-width:100%)。
40
+ * 务必给定或接受默认值, 否则无法预留空间 (CLS)。运行时用 setSize() 改。
41
+ */
38
42
  width?: number | string;
39
43
  height?: number | string;
40
44
  position?: XiaochunPosition;
@@ -58,6 +62,10 @@ export interface XiaochunOptions {
58
62
  * 需要 capabilities.gestures (旧版 /embed 无效, 触发一次 error{code:'unsupported'})。
59
63
  */
60
64
  resizable?: boolean | XiaochunResizeLimits;
65
+ /**
66
+ * iframe 界面语言 ('zh-CN' | 'en' | 'ja')。显式指定时优先于 iframe 自己记住的用户选择; 不写 = 用 iframe localStorage 里用户上次在语言按钮里选的, 再没有就跟随浏览器语言 (默认 zh-CN)。
67
+ * 运行中可用 `setConfig({ lang })` 改 (不持久化); 用户点内置语言按钮 (`ui` 里的 'lang') 会存进 iframe 自己的 localStorage, 并发 `lang-changed` 事件。
68
+ */
61
69
  lang?: XcLang;
62
70
  /** 初始服装: 内置服装 id (如 'xiaochun_maid', 见 getOutfits())。未知 id 回退默认服装, 并触发 error{code:'unknown_id'}。 */
63
71
  outfit?: string;
@@ -79,17 +87,49 @@ export interface XiaochunOptions {
79
87
  * 优先级: 显式的 outfit / scene 选项 > 已保存的偏好 > 默认。只有 setOutfit / setScene 引起的变化会写入。
80
88
  */
81
89
  persist?: false | 'host' | (string & {});
90
+ /**
91
+ * 记住用户拖动 / 缩放后的**位置和大小**(可选,默认 false = 不记)。保存到**宿主页**的 localStorage(读写都包了 try/catch,被禁用 / 配额满只是不记忆),
92
+ * 在 draggable / resizable 手势**结束**时写入,下次创建时恢复。
93
+ * false / 不写 不记
94
+ * true 存到 localStorage['xiaochun:box']
95
+ * '<名字>' 存到 localStorage['xiaochun:box:<名字>'](同一站点多个实例 / 多个页面互不覆盖时用;也可当版本号,改名即丢弃旧值)
96
+ * 优先级:显式的 width / height 选项 > 已保存的 > 默认(600x1080)。位置没有对应的显式选项(`position` 只是预设锚点),已保存的位置始终生效。
97
+ * 所以想让"用户缩放过的大小"被恢复,就**不要**传 width / height(用默认值)。
98
+ * 只在对应手势打开时恢复(需要 draggable 或 resizable;没开手势的实例不会被旧值改位置);保存的 `position` 模式(inline / bottom-right / bottom-left)与当前不同则忽略。
99
+ * 恢复时钳制到**当前视口**:大小夹在 [最小, 视口](最小 = resizable 的 minWidth/minHeight,默认 120x180),悬浮模式的位置夹进视口;
100
+ * 内联模式横向夹进视口、纵向只保证不越过文档顶部(内联盒子可以在滚动的页面里)。窗口之后变小时,既有的 resize 监听也会继续把它夹回视口。
101
+ * 清除:`clearPersistedBox()`(只删存储)或 `clearPersistedBox({ reset: true })`(同时回到初始位置 / 大小)。
102
+ */
103
+ persistBox?: boolean | (string & {});
82
104
  /**
83
105
  * 要显示的 iframe 内置界面部件 (数组, 默认不写 = 全不显示):
84
106
  * 'chat' 底部聊天栏 (对话走 WebLLM, 会多下载一部分代码)
85
107
  * 'bubble' 头顶气泡 (说话文本 / 状态)
86
108
  * 'outfit' 换装按钮 } 外观/文案/交互与主站 TopHeader 一致; 点按钮走和 setOutfit() / setScene() 同一条白名单 + 串行 (last-wins) 路径,
109
+ * 'lang' 语言切换按钮 (中文 / English / 日本語; 与主站 TopHeader 同一个组件, 用户的选择记在 iframe 自己的 localStorage, 发 lang-changed 事件)
110
+ * 'github' GitHub 按钮 (新标签页打开项目仓库)
87
111
  * 'scene' 换场景按钮 } 照常触发 outfit-changed / scene-changed, 宿主用 SDK 换装时按钮状态同步。偏好由 iframe 自己的 localStorage 记住 (显式 outfit/scene 优先); 想自己存也可用 persist 或监听事件。
88
112
  * 例: `ui: ['outfit', 'scene']`。未知名字忽略并 console.warn。创建期选项: 变化会重建 iframe。
89
- * 'outfit' / 'scene' 只对新版 /embed 有效 (旧版忽略未知部件名)。
113
+ * 'outfit' / 'scene' / 'lang' / 'github' 只对新版 /embed 有效 (旧版忽略未知部件名)。
90
114
  * @deprecated 布尔写法: `ui: true` (0.1.14 的旧写法) 等价于 `['chat', 'bubble']` 并 console.warn; 不要再用。
91
115
  */
92
116
  ui?: XcUiPart[] | boolean;
117
+ /**
118
+ * 内置界面 (ui 里的按钮 + 聊天栏 chat) 的显示策略, 默认 **'transparent'**, 与 Tauri 桌面端逐字一致: 只有透明场景默认隐藏, 单击角色出现
119
+ * (按住拖动 / 长按拖不算点击, 所以不与 `draggable` 冲突), 再单击角色或单击空白收起, 10 秒无操作自动收起 (悬停在按钮 / 聊天栏上、菜单打开、输入中不收); 触屏同样是轻点角色。亮 / 暗场景常显。
120
+ * 'transparent' 默认: 只有透明场景点击出现
121
+ * true 所有场景都点击出现 (亮 / 暗场景也默认隐藏)
122
+ * false 一直显示 (0.1.15 及以前的行为)
123
+ * 创建期选项 (变化会重建 iframe); 运行中可用 `setConfig({ uiAutoHide })`。旧版 /embed (握手里没有 capabilities.ui) 忽略它, 界面常显。
124
+ * 注意: 透明场景下点击宿主页的空白处到不了 iframe (穿透), 所以那里只能靠 10 秒超时 / 再点角色收起。
125
+ */
126
+ uiAutoHide?: XcUiAutoHide;
127
+ /**
128
+ * 相机: `{ fov, distance, height, intro }` (见 XcCamera; 范围见 XC_CAMERA_RANGES: fov 15–60°、distance 1–15m、height ±1m, 越界夹到边界), 缺省 = 不覆盖。
129
+ * 创建期通过 URL 传给 iframe (模型加载前生效, 首次取景 / 推镜头直接按它来); 运行中用 `setConfig({ camera })` 改
130
+ * (缺省键不变, null = 恢复默认; 会立刻重新取景并取消进行中的推镜头)。旧版 /embed (握手里没有 capabilities.camera) 忽略它。
131
+ */
132
+ camera?: XcCamera;
93
133
  heavy?: XcHeavyMode;
94
134
  /**
95
135
  * 自动预取服装资源到 iframe 的 IndexedDB (之后 setOutfit 不走网络), 默认 false。
@@ -179,6 +219,8 @@ export interface XiaochunEvents {
179
219
  'outfit-changed': XcOutfitChangedPayload;
180
220
  /** 场景已生效 (含握手后上报的当前场景, initial:true)。 */
181
221
  'scene-changed': XcSceneChangedPayload;
222
+ /** iframe 界面语言已生效 (含握手后上报的当前语言, initial:true; 用户点语言按钮 / setConfig({ lang }) 引起的变化)。 */
223
+ 'lang-changed': XcLangChangedPayload;
182
224
  /** 用户拖动头像 (draggable): start / move / end 各发一次, 位置已限幅。 */
183
225
  move: XiaochunBoxEvent;
184
226
  /** 用户缩放头像 (resizable): start / move / end 各发一次, 尺寸已限幅。 */
@@ -242,8 +284,8 @@ export interface XiaochunInstance {
242
284
  name?: string;
243
285
  }): Promise<void>;
244
286
  setConfig(cfg: XcConfig): Promise<void>;
245
- /** 运行时设置外壳尺寸 (数字 = px, 字符串 = CSS 长度); 不重建 iframe。用户缩放后宿主要同步自己的状态时用 `resize` 事件。 */
246
- setSize(width: number | string, height: number | string): void;
287
+ /** 运行时设置外壳尺寸 (数字 = px, 字符串 = CSS 长度, undefined = 恢复默认 600x1080 (受视口限制)); 不重建 iframe。用户缩放后宿主要同步自己的状态时用 `resize` 事件。 */
288
+ setSize(width: number | string | undefined, height: number | string | undefined): void;
247
289
  /** 外壳当前在视口中的位置与尺寸 (CSS px)。 */
248
290
  getBox(): {
249
291
  left: number;
@@ -251,6 +293,10 @@ export interface XiaochunInstance {
251
293
  width: number;
252
294
  height: number;
253
295
  };
296
+ /** 清除 `persistBox` 保存的位置 / 大小(没开 persistBox 时是空操作)。`reset: true` 另外把当前盒子还原到初始位置和大小(按 width / height 选项或默认值,不重建 iframe)。 */
297
+ clearPersistedBox(opts?: {
298
+ reset?: boolean;
299
+ }): void;
254
300
  /** 运行时开关手势拖动 (等价于 `draggable` 选项), 不重建 iframe。 */
255
301
  /** 改外壳圆角 (数字 px / CSS 长度); 传 undefined 恢复默认 (非透明 20px / 透明 0)。 */
256
302
  setBorderRadius(radius: number | string | undefined): void;
@@ -265,4 +311,17 @@ export interface XiaochunInstance {
265
311
  destroy(): void;
266
312
  on<K extends keyof XiaochunEvents>(event: K, cb: (payload: XiaochunEvents[K]) => void): () => void;
267
313
  }
314
+ /** 默认尺寸 (px): 600x1080。只在没显式传 width / height 时使用, 并受视口限制 (见 defaultWidthCss / defaultHeightCss)。 */
315
+ export declare const XC_DEFAULT_WIDTH = 600;
316
+ export declare const XC_DEFAULT_HEIGHT = 1080;
317
+ /**
318
+ * 默认宽: 600px 但不超过视口宽 (min(600px, 100vw))。不用 min(600px, 100%): 百分比在 "按内容收缩" 的容器 (inline-block / 绝对定位 / <xiaochun-avatar> 的 :host)
319
+ * 里会循环依赖而塌成 0。比视口更窄的容器 (如 300px 的侧栏) 由外壳的 max-width:100% 再钳一次, 所以窄屏 / 窄容器都不会溢出。
320
+ */
321
+ export declare const defaultWidthCss: (floating?: boolean) => string;
322
+ /**
323
+ * 默认高: 1080px 但不超过视口高 (小窗口 / 手机横屏钳制)。浏览器支持时用 svh (移动端地址栏伸缩时也不溢出), 否则 vh。
324
+ * SSR 下 (没有 CSS.supports) 一律 vh, 与 React 服务端渲染的首帧保持一致。
325
+ */
326
+ export declare const defaultHeightCss: (floating?: boolean) => string;
268
327
  export declare function createXiaochun(options: XiaochunOptions): XiaochunInstance;