@firetable/project-xiaochun 0.1.12

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.
@@ -0,0 +1,165 @@
1
+ /*! @firetable/project-xiaochun v0.1.12 | MIT | https://github.com/FireTable/project-xiaochun */
2
+ import {
3
+ createXiaochun
4
+ } from "./chunk-UAIDX54S.js";
5
+
6
+ // src/avatar-element.ts
7
+ var OBSERVED = [
8
+ "src",
9
+ "model",
10
+ "lang",
11
+ "mic",
12
+ "transparent",
13
+ "draggable",
14
+ "position",
15
+ "size",
16
+ "lazy",
17
+ "paused",
18
+ "placeholder",
19
+ "heavy",
20
+ "ui",
21
+ "controls",
22
+ "allowed-origins"
23
+ ];
24
+ var REBUILD = /* @__PURE__ */ new Set(["src", "draggable", "position", "size", "lazy", "placeholder", "heavy", "ui", "controls", "allowed-origins", "transparent"]);
25
+ function boolAttr(el, name, dflt) {
26
+ const v = el.getAttribute(name);
27
+ if (v === null) return dflt;
28
+ return v !== "false";
29
+ }
30
+ function parseSize(v) {
31
+ if (!v) return { width: "320px", height: "480px" };
32
+ const m = v.trim().split(/x/i);
33
+ const px = (s) => /^\d+(\.\d+)?$/.test(s) ? `${s}px` : s;
34
+ if (m.length >= 2) return { width: px(m[0]), height: px(m[1]) };
35
+ const w = parseFloat(m[0]);
36
+ return Number.isFinite(w) && /^\d+(\.\d+)?$/.test(m[0]) ? { width: `${w}px`, height: `${Math.round(w * 1.5)}px` } : { width: m[0], height: "480px" };
37
+ }
38
+ function createElementClass() {
39
+ class XiaochunAvatarElementImpl extends HTMLElement {
40
+ constructor() {
41
+ super(...arguments);
42
+ /** 底层 SDK 实例 (连接 DOM 后可用)。 */
43
+ this.client = null;
44
+ this.mountEl = null;
45
+ this.rebuildScheduled = false;
46
+ }
47
+ static get observedAttributes() {
48
+ return [...OBSERVED];
49
+ }
50
+ connectedCallback() {
51
+ if (!this.shadowRoot) {
52
+ const root = this.attachShadow({ mode: "open" });
53
+ root.innerHTML = '<style>:host{display:inline-block;max-width:100%}:host([hidden]){display:none}</style><div part="mount"></div>';
54
+ }
55
+ this.mountEl = this.shadowRoot.querySelector("div");
56
+ this.build();
57
+ }
58
+ disconnectedCallback() {
59
+ this.destroy();
60
+ }
61
+ attributeChangedCallback(name, oldV, newV) {
62
+ if (!this.client || oldV === newV) return;
63
+ if (REBUILD.has(name)) {
64
+ if (!this.rebuildScheduled) {
65
+ this.rebuildScheduled = true;
66
+ queueMicrotask(() => {
67
+ this.rebuildScheduled = false;
68
+ if (this.isConnected) {
69
+ this.destroy();
70
+ this.build();
71
+ }
72
+ });
73
+ }
74
+ return;
75
+ }
76
+ const c = this.client;
77
+ if (name === "model" && newV) void c.setModel(newV).catch(() => {
78
+ });
79
+ else if (name === "lang" && newV) void c.setConfig({ lang: newV }).catch(() => {
80
+ });
81
+ else if (name === "mic") void c.mic(boolAttr(this, "mic", false)).catch(() => {
82
+ });
83
+ else if (name === "paused") {
84
+ if (boolAttr(this, "paused", false)) c.pause();
85
+ else c.resume();
86
+ }
87
+ }
88
+ build() {
89
+ if (!this.mountEl || this.client) return;
90
+ const { width, height } = parseSize(this.getAttribute("size"));
91
+ const placeholder = this.getAttribute("placeholder");
92
+ const allowed = this.getAttribute("allowed-origins");
93
+ const lazyAttr = this.getAttribute("lazy");
94
+ const lazy = lazyAttr === "click" ? "click" : lazyAttr === "false" ? false : true;
95
+ const c = createXiaochun({
96
+ container: this.mountEl,
97
+ src: this.getAttribute("src") || void 0,
98
+ allowedOrigins: allowed ? allowed.split(",").map((s) => s.trim()).filter(Boolean) : void 0,
99
+ lazy,
100
+ placeholder: placeholder === "none" ? false : placeholder || void 0,
101
+ transparent: boolAttr(this, "transparent", true),
102
+ // 悬浮头像默认透明
103
+ width,
104
+ height,
105
+ position: this.getAttribute("position") ?? "inline",
106
+ draggable: boolAttr(this, "draggable", false),
107
+ lang: this.getAttribute("lang") ?? void 0,
108
+ model: this.getAttribute("model") ?? void 0,
109
+ heavy: this.getAttribute("heavy") ?? void 0,
110
+ ui: boolAttr(this, "ui", false),
111
+ controls: boolAttr(this, "controls", false)
112
+ });
113
+ this.client = c;
114
+ const fwd = (ev, name) => c.on(ev, (detail) => this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true })));
115
+ fwd("ready", "xc-ready");
116
+ fwd("progress", "xc-progress");
117
+ fwd("state", "xc-state");
118
+ fwd("stt", "xc-stt");
119
+ fwd("utterance", "xc-utterance");
120
+ fwd("error", "xc-error");
121
+ if (boolAttr(this, "paused", false)) c.pause();
122
+ if (boolAttr(this, "mic", false)) void c.ready.then(() => c.mic(true)).catch(() => {
123
+ });
124
+ }
125
+ say(text, opts) {
126
+ return this.requireClient().say(text, opts);
127
+ }
128
+ speakAudio(source, opts) {
129
+ return this.requireClient().speakAudio(source, opts);
130
+ }
131
+ speakAudioStream(opts) {
132
+ return this.requireClient().speakAudioStream(opts);
133
+ }
134
+ motion(m) {
135
+ return this.requireClient().motion(m);
136
+ }
137
+ expression(name) {
138
+ return this.requireClient().expression(name);
139
+ }
140
+ destroy() {
141
+ this.client?.destroy();
142
+ this.client = null;
143
+ }
144
+ requireClient() {
145
+ if (!this.client) throw new Error("<xiaochun-avatar> is not connected");
146
+ return this.client;
147
+ }
148
+ }
149
+ return XiaochunAvatarElementImpl;
150
+ }
151
+ var XIAOCHUN_ELEMENT_TAG = "xiaochun-avatar";
152
+ function defineXiaochunElement(tag = XIAOCHUN_ELEMENT_TAG) {
153
+ if (typeof customElements === "undefined" || typeof HTMLElement === "undefined") return null;
154
+ const existing = customElements.get(tag);
155
+ if (existing) return existing;
156
+ const ctor = createElementClass();
157
+ customElements.define(tag, ctor);
158
+ return ctor;
159
+ }
160
+
161
+ export {
162
+ XIAOCHUN_ELEMENT_TAG,
163
+ defineXiaochunElement
164
+ };
165
+ //# sourceMappingURL=chunk-XKLOFX52.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/avatar-element.ts"],
4
+ "sourcesContent": ["/**\n * avatar-element.ts \u2014 `<xiaochun-avatar>` Web Component (Shadow DOM \u5185\u5305 iframe)\u3002\n *\n * \u5C5E\u6027: src model lang mic transparent draggable position size lazy paused placeholder heavy ui controls allowed-origins\n * - \u5E03\u5C14\u5C5E\u6027: \u7F3A\u7701\u53D6\u9ED8\u8BA4\u503C; \"\" / \"true\" = true; \"false\" = false\u3002\n * - size=\"320x480\" \u6216 size=\"320\" (\u9AD8 = \u5BBD \u00D7 1.5); \u4E5F\u53EF\u5199 CSS \u957F\u5EA6 \"100%x480px\"\u3002\n * - model: \u5185\u7F6E\u670D\u88C5 key (xiaochun_maid) \u6216 https .vrm/.vrmaddon/.vrmbase URL\u3002\n * \u6837\u5F0F (CSS \u81EA\u5B9A\u4E49\u5C5E\u6027, \u53EF\u5199\u5728 <xiaochun-avatar> \u4E0A\u6216\u4EFB\u610F\u7956\u5148\u4E0A; \u53D6\u503C\u8303\u56F4/\u6548\u679C\u89C1 client.ts \u91CC\u7684\u6CE8\u91CA\u4E0E docs/EMBED.md \u00A7\u6837\u5F0F):\n * --xc-radius --xc-shadow --xc-z-index --xc-offset-x --xc-offset-y --xc-bg\n * \u53EF\u7528 ::part() \u5B9A\u5236\u5916\u58F3: ::part(mount) ::part(wrapper) ::part(iframe) ::part(placeholder) (iframe \u5185\u90E8\u4E0D\u53EF\u88AB\u5BBF\u4E3B CSS \u5F71\u54CD)\n * \u4E8B\u4EF6 (CustomEvent, composed, detail = \u534F\u8BAE payload): xc-ready(\u6A21\u578B\u52A0\u8F7D\u5B8C) xc-progress xc-state xc-stt xc-utterance xc-error\n * \u65B9\u6CD5: say(text) speakAudio(source, opts) speakAudioStream(opts) motion(m) expression(name) destroy() (+ client \u5C5E\u6027\u62FF\u5230\u5B8C\u6574 SDK \u5B9E\u4F8B)\n */\nimport {\n createXiaochun,\n type XiaochunAudioOptions,\n type XiaochunAudioSource,\n type XiaochunAudioStream,\n type XiaochunInstance,\n type XiaochunLazy,\n type XiaochunPosition,\n} from './client';\nimport type { XcExpressionPayload, XcHeavyMode, XcLang, XcMotionPayload } from './protocol';\n\nconst OBSERVED = [\n 'src', 'model', 'lang', 'mic', 'transparent', 'draggable', 'position', 'size', 'lazy',\n 'paused', 'placeholder', 'heavy', 'ui', 'controls', 'allowed-origins',\n] as const;\n/** \u6539\u4E86\u8FD9\u4E9B\u8981\u91CD\u5EFA iframe; \u5176\u4F59\u53EF\u4EE5\u70ED\u66F4\u65B0\u3002 */\nconst REBUILD = new Set(['src', 'draggable', 'position', 'size', 'lazy', 'placeholder', 'heavy', 'ui', 'controls', 'allowed-origins', 'transparent']);\n\nfunction boolAttr(el: Element, name: string, dflt: boolean): boolean {\n const v = el.getAttribute(name);\n if (v === null) return dflt;\n return v !== 'false';\n}\n\nfunction parseSize(v: string | null): { width: string; height: string } {\n if (!v) return { width: '320px', height: '480px' };\n const m = v.trim().split(/x/i);\n const px = (s: string) => (/^\\d+(\\.\\d+)?$/.test(s) ? `${s}px` : s);\n if (m.length >= 2) return { width: px(m[0]), height: px(m[1]) };\n const w = parseFloat(m[0]);\n return Number.isFinite(w) && /^\\d+(\\.\\d+)?$/.test(m[0]) ? { width: `${w}px`, height: `${Math.round(w * 1.5)}px` } : { width: m[0], height: '480px' };\n}\n\n/** \u516C\u5F00\u7C7B\u578B (\u7C7B\u672C\u8EAB\u5728\u6D4F\u89C8\u5668\u91CC\u624D\u521B\u5EFA, \u8FD9\u6837 SSR / Node \u4E0B import \u672C\u5305\u4E0D\u4F1A\u56E0 HTMLElement \u7F3A\u5931\u800C\u629B\u9519)\u3002 */\nexport interface XiaochunAvatarElement extends HTMLElement {\n client: XiaochunInstance | null;\n say(text: string, opts?: { mode?: 'speak' | 'chat' }): Promise<void>;\n /** \u76F4\u63A5\u64AD\u653E\u5BBF\u4E3B\u7ED9\u7684\u97F3\u9891 (\u4E0D\u8D70 TTS), \u540C\u65F6\u7531 EMAGE \u751F\u6210\u52A8\u4F5C + \u53E3\u578B\u3002 */\n speakAudio(source: XiaochunAudioSource, opts?: XiaochunAudioOptions): Promise<void>;\n speakAudioStream(opts: XiaochunAudioOptions & { sampleRate: number }): XiaochunAudioStream;\n motion(m: XcMotionPayload | string): Promise<void>;\n expression(name: XcExpressionPayload['name']): Promise<void>;\n destroy(): void;\n}\n\nfunction createElementClass(): CustomElementConstructor {\n class XiaochunAvatarElementImpl extends HTMLElement {\n static get observedAttributes() { return [...OBSERVED]; }\n\n /** \u5E95\u5C42 SDK \u5B9E\u4F8B (\u8FDE\u63A5 DOM \u540E\u53EF\u7528)\u3002 */\n client: XiaochunInstance | null = null;\n private mountEl: HTMLDivElement | null = null;\n private rebuildScheduled = false;\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const root = this.attachShadow({ mode: 'open' });\n // :host \u9ED8\u8BA4 inline-block + max-width:100% (\u914D\u5408 SDK \u7684\u56FA\u5B9A\u5BBD\u9AD8, \u4E0D\u4F1A\u6491\u7834\u7A84\u5C4F\u5BB9\u5668)\u3002\n // \u60AC\u6D6E (position=bottom-right|bottom-left) \u65F6 SDK \u5185\u90E8\u7528 position:fixed, \u4E0D\u53D7 :host \u76D2\u6A21\u578B\u5F71\u54CD\u3002\n root.innerHTML = '<style>:host{display:inline-block;max-width:100%}:host([hidden]){display:none}</style><div part=\"mount\"></div>';\n }\n this.mountEl = this.shadowRoot!.querySelector('div');\n this.build();\n }\n\n disconnectedCallback(): void {\n this.destroy();\n }\n\n attributeChangedCallback(name: string, oldV: string | null, newV: string | null): void {\n if (!this.client || oldV === newV) return;\n if (REBUILD.has(name)) {\n if (!this.rebuildScheduled) {\n this.rebuildScheduled = true;\n queueMicrotask(() => { this.rebuildScheduled = false; if (this.isConnected) { this.destroy(); this.build(); } });\n }\n return;\n }\n const c = this.client;\n if (name === 'model' && newV) void c.setModel(newV).catch(() => {});\n else if (name === 'lang' && newV) void c.setConfig({ lang: newV as XcLang }).catch(() => {});\n else if (name === 'mic') void c.mic(boolAttr(this, 'mic', false)).catch(() => {});\n else if (name === 'paused') { if (boolAttr(this, 'paused', false)) c.pause(); else c.resume(); }\n }\n\n private build(): void {\n if (!this.mountEl || this.client) return;\n const { width, height } = parseSize(this.getAttribute('size'));\n const placeholder = this.getAttribute('placeholder');\n const allowed = this.getAttribute('allowed-origins');\n const lazyAttr = this.getAttribute('lazy');\n const lazy: XiaochunLazy = lazyAttr === 'click' ? 'click' : lazyAttr === 'false' ? false : true;\n const c = createXiaochun({\n container: this.mountEl,\n src: this.getAttribute('src') || undefined,\n allowedOrigins: allowed ? allowed.split(',').map((s) => s.trim()).filter(Boolean) : undefined,\n lazy,\n placeholder: placeholder === 'none' ? false : placeholder || undefined,\n transparent: boolAttr(this, 'transparent', true), // \u60AC\u6D6E\u5934\u50CF\u9ED8\u8BA4\u900F\u660E\n width, height,\n position: (this.getAttribute('position') as XiaochunPosition | null) ?? 'inline',\n draggable: boolAttr(this, 'draggable', false),\n lang: (this.getAttribute('lang') as XcLang | null) ?? undefined,\n model: this.getAttribute('model') ?? undefined,\n heavy: (this.getAttribute('heavy') as XcHeavyMode | null) ?? undefined,\n ui: boolAttr(this, 'ui', false),\n controls: boolAttr(this, 'controls', false),\n });\n this.client = c;\n const fwd = (ev: string, name: string) => c.on(ev as 'ready', (detail) =>\n this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true })));\n fwd('ready', 'xc-ready');\n fwd('progress', 'xc-progress');\n fwd('state', 'xc-state');\n fwd('stt', 'xc-stt');\n fwd('utterance', 'xc-utterance');\n fwd('error', 'xc-error');\n if (boolAttr(this, 'paused', false)) c.pause();\n if (boolAttr(this, 'mic', false)) void c.ready.then(() => c.mic(true)).catch(() => {});\n }\n\n say(text: string, opts?: { mode?: 'speak' | 'chat' }): Promise<void> {\n return this.requireClient().say(text, opts);\n }\n speakAudio(source: XiaochunAudioSource, opts?: XiaochunAudioOptions): Promise<void> {\n return this.requireClient().speakAudio(source, opts);\n }\n speakAudioStream(opts: XiaochunAudioOptions & { sampleRate: number }): XiaochunAudioStream {\n return this.requireClient().speakAudioStream(opts);\n }\n motion(m: XcMotionPayload | string): Promise<void> { return this.requireClient().motion(m); }\n expression(name: XcExpressionPayload['name']): Promise<void> { return this.requireClient().expression(name); }\n\n destroy(): void {\n this.client?.destroy();\n this.client = null;\n }\n\n private requireClient(): XiaochunInstance {\n if (!this.client) throw new Error('<xiaochun-avatar> is not connected');\n return this.client;\n }\n }\n\n return XiaochunAvatarElementImpl;\n}\n\nexport const XIAOCHUN_ELEMENT_TAG = 'xiaochun-avatar';\n\n/** \u6CE8\u518C\u81EA\u5B9A\u4E49\u5143\u7D20 (\u5E42\u7B49)\u3002import '@firetable/project-xiaochun/element' \u4F1A\u81EA\u52A8\u8C03\u7528\u3002 */\nexport function defineXiaochunElement(tag: string = XIAOCHUN_ELEMENT_TAG): CustomElementConstructor | null {\n if (typeof customElements === 'undefined' || typeof HTMLElement === 'undefined') return null;\n const existing = customElements.get(tag);\n if (existing) return existing;\n const ctor = createElementClass();\n customElements.define(tag, ctor);\n return ctor;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * client.ts — 无框架、零运行时依赖的 Project XiaoChun 嵌入 SDK。
3
+ *
4
+ * const xc = createXiaochun({ container: '#avatar', transparent: true });
5
+ * await xc.ready; // 模型加载完成 (xc.loaded)
6
+ * await xc.say('你好呀'); // 念完 (xc.utterance end) 才 resolve
7
+ *
8
+ * 默认 lazy: 先放固定尺寸的占位 (不产生 CLS), 浏览器空闲 + 进入视口 + (或点击/调用 API) 之后才创建 iframe。
9
+ */
10
+ import { type XcAudioFormat, type XcAudioOptions, type XcConfig, type XcErrorCode, type XcExpressionPayload, type XcHeavyMode, type XcHitRegionPayload, type XcLang, type XcLoadProgressPayload, type XcMotionPayload, type XcReadyPayload, type XcSttPayload, type XcStatePayload, type XcUtterancePayload } from './protocol';
11
+ export type XiaochunPosition = 'inline' | 'bottom-right' | 'bottom-left';
12
+ /** lazy: false=立即创建; true/'idle'=进入视口且空闲后创建; 'click'=仅点击占位或调用 API 时创建。 */
13
+ export type XiaochunLazy = boolean | 'idle' | 'click';
14
+ export interface XiaochunOptions {
15
+ /** 挂载容器 (元素或选择器)。 */
16
+ container: HTMLElement | string;
17
+ /** /embed 页面地址, 默认官方部署。自建/本地调试时覆盖。 */
18
+ src?: string;
19
+ /** iframe 的 origin, 默认由 src 推导。所有握手/消息校验都以它为准。 */
20
+ origin?: string;
21
+ /** 额外允许发 xc.ready 的 origin (如 CDN 重定向); 默认只有 origin。不支持 '*'。 */
22
+ allowedOrigins?: string[];
23
+ lazy?: XiaochunLazy;
24
+ /** 距视口多少 px 内就开始加载 (IntersectionObserver rootMargin)。调大=更早加载, 更耗流量; 调小=更省但滚到时可能还没好。默认 200。 */
25
+ lazyMargin?: number;
26
+ /** 占位图 URL / 元素; false = 不要占位。 */
27
+ placeholder?: string | HTMLElement | false;
28
+ transparent?: boolean;
29
+ /** 固定尺寸, 数字=px, 字符串=CSS 长度。务必给定, 否则无法预留空间 (CLS)。默认 320x480。 */
30
+ width?: number | string;
31
+ height?: number | string;
32
+ position?: XiaochunPosition;
33
+ /** 悬浮模式下允许拖动 (左上角手柄)。 */
34
+ draggable?: boolean;
35
+ lang?: XcLang;
36
+ /** 初始服装: 内置 key (如 'xiaochun_maid') 或 https .vrm/.vrmaddon URL。 */
37
+ model?: string;
38
+ /** 显示 iframe 内置 ChatBar (默认 false)。 */
39
+ ui?: boolean;
40
+ heavy?: XcHeavyMode;
41
+ /** 放开 iframe 内滚轮缩放 (默认锁, 防止吞宿主滚动)。 */
42
+ controls?: boolean;
43
+ /** 视口外自动 xc.pause / 回来 xc.resume, 默认 true。 */
44
+ autoPause?: boolean;
45
+ /** 透明模式下按"是否点在角色上"切换 iframe 的 pointer-events, 默认 = transparent。 */
46
+ passthrough?: boolean;
47
+ /** iframe sandbox 属性; false = 不加。默认允许 scripts/same-origin/popups。 */
48
+ sandbox?: string | false;
49
+ /** 握手超时 (ms), 超时 emit error{code:'timeout'}。调大适合慢网络, 调小反馈更快。默认 20000。 */
50
+ handshakeTimeout?: number;
51
+ zIndex?: number;
52
+ }
53
+ /** speakAudio / speakAudioStream 的选项。 */
54
+ export interface XiaochunAudioOptions extends XcAudioOptions {
55
+ /** 编码; 省略 = 'encoded' (容器格式)。传 ArrayBuffer 的原始 PCM 时必须写 'pcm16' | 'float32'。 */
56
+ format?: XcAudioFormat;
57
+ /** MIME 提示 (如 'audio/mpeg'); Blob 会自动取 blob.type。 */
58
+ mimeType?: string;
59
+ /** 原始 PCM 的采样率 (8000~96000, 整段默认 16000, 流式必填); 容器格式忽略。 */
60
+ sampleRate?: number;
61
+ /** 原始 PCM 声道数 (交错), 1 或 2。 */
62
+ channels?: 1 | 2;
63
+ /**
64
+ * ArrayBuffer 是否用 transferable 零拷贝发送 (默认 true)。
65
+ * true: 发出后传入的那个 ArrayBuffer 会被 detach (byteLength 变 0), 适合大音频;
66
+ * false: 结构化克隆一份, 原 buffer 仍可用, 但多一次拷贝。TypedArray 视图总是拷贝其范围后再转移。
67
+ */
68
+ transfer?: boolean;
69
+ /** URL 字符串的获取位置: 'host' (默认) = 宿主页 fetch 后转移给 iframe, 不需要 iframe 能访问该 URL; 'frame' = 把 URL 交给 iframe fetch (需 https + CORS)。 */
70
+ fetch?: 'host' | 'frame';
71
+ /** 触发后立即打断这次说话 (发 xc.audio.end{abort:true})。 */
72
+ signal?: AbortSignal;
73
+ }
74
+ export type XiaochunAudioSource = ArrayBuffer | ArrayBufferView | Blob | string;
75
+ /** 流式音频句柄: write 原始 PCM 块, end 收尾, abort 立即打断; done 在说话结束 (或被抢占) 时 resolve。 */
76
+ export interface XiaochunAudioStream {
77
+ write(data: Int16Array | Float32Array | ArrayBuffer): void;
78
+ end(): void;
79
+ abort(): void;
80
+ readonly done: Promise<void>;
81
+ }
82
+ export type XiaochunErrorCode = XcErrorCode | 'timeout';
83
+ export interface XiaochunError {
84
+ code: XiaochunErrorCode;
85
+ message: string;
86
+ command?: string;
87
+ }
88
+ export interface XiaochunEvents {
89
+ /** xc.ready 握手成功 (协议层)。 */
90
+ handshake: XcReadyPayload;
91
+ /** 模型加载完成 (= xc.loaded)。 */
92
+ ready: {
93
+ model: string;
94
+ };
95
+ progress: XcLoadProgressPayload;
96
+ state: XcStatePayload;
97
+ stt: XcSttPayload;
98
+ utterance: XcUtterancePayload;
99
+ 'hit-region': XcHitRegionPayload;
100
+ error: XiaochunError;
101
+ destroy: undefined;
102
+ }
103
+ export interface XiaochunInstance {
104
+ readonly element: HTMLElement;
105
+ readonly iframe: HTMLIFrameElement | null;
106
+ /** 模型加载完成时 resolve; destroy 时 reject。 */
107
+ readonly ready: Promise<void>;
108
+ /** 手动创建 iframe (lazy 模式下等价于"用户点击")。 */
109
+ activate(): void;
110
+ say(text: string, opts?: {
111
+ mode?: 'speak' | 'chat';
112
+ }): Promise<void>;
113
+ /**
114
+ * 直接播放宿主给的音频 (不经过 TTS): iframe 内解码 → EMAGE 生成动作 + 口型, 念完 (xc.utterance end) 才 resolve。
115
+ * 首次调用才加载 EMAGE 模型 (首次会慢); opts.motion=false 则只播放不加载。
116
+ */
117
+ speakAudio(source: XiaochunAudioSource, opts?: XiaochunAudioOptions): Promise<void>;
118
+ /** 流式音频 (原始 PCM 分块)。sampleRate 必填; 首个 write 开启说话。 */
119
+ speakAudioStream(opts: XiaochunAudioOptions & {
120
+ sampleRate: number;
121
+ }): XiaochunAudioStream;
122
+ motion(m: XcMotionPayload | string): Promise<void>;
123
+ expression(name: XcExpressionPayload['name']): Promise<void>;
124
+ /** TODO: 协议已预留, /embed 暂未实现 → 会收到 error{code:'unsupported'}。 */
125
+ lookAt(x: number, y: number): Promise<void>;
126
+ setModel(m: string | {
127
+ url?: string;
128
+ outfit?: string;
129
+ name?: string;
130
+ }): Promise<void>;
131
+ setConfig(cfg: XcConfig): Promise<void>;
132
+ startListening(): Promise<void>;
133
+ stopListening(): Promise<void>;
134
+ mic(enabled: boolean): Promise<void>;
135
+ pause(): void;
136
+ resume(): void;
137
+ destroy(): void;
138
+ on<K extends keyof XiaochunEvents>(event: K, cb: (payload: XiaochunEvents[K]) => void): () => void;
139
+ }
140
+ export declare function createXiaochun(options: XiaochunOptions): XiaochunInstance;