@firetable/project-xiaochun 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-CN.md +144 -18
- package/README.md +144 -18
- package/dist/avatar-element.d.ts +23 -6
- package/dist/chunks/{chunk-UB2HFUBS.js → chunk-KKRZ3SUD.js} +50 -2
- package/dist/chunks/chunk-KKRZ3SUD.js.map +7 -0
- package/dist/chunks/chunk-KQSKHPVF.js +957 -0
- package/dist/chunks/chunk-KQSKHPVF.js.map +7 -0
- package/dist/chunks/{chunk-NKR4JG3C.js → chunk-T6EHFEEZ.js} +83 -9
- package/dist/chunks/chunk-T6EHFEEZ.js.map +7 -0
- package/dist/client.d.ts +134 -6
- package/dist/element.cjs +572 -71
- package/dist/element.cjs.map +4 -4
- package/dist/element.js +4 -4
- package/dist/gesture-box.d.ts +72 -0
- package/dist/index.cjs +599 -71
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +22 -4
- package/dist/index.js.map +1 -1
- package/dist/loader.global.js +2 -2
- package/dist/loader.global.js.map +4 -4
- package/dist/protocol.cjs +49 -1
- package/dist/protocol.cjs.map +2 -2
- package/dist/protocol.d.ts +180 -9
- package/dist/protocol.js +20 -2
- package/dist/react.cjs +540 -77
- package/dist/react.cjs.map +4 -4
- package/dist/react.d.ts +17 -1
- package/dist/react.js +50 -15
- package/dist/react.js.map +2 -2
- package/package.json +5 -3
- package/dist/chunks/chunk-NKR4JG3C.js.map +0 -7
- package/dist/chunks/chunk-UB2HFUBS.js.map +0 -7
- package/dist/chunks/chunk-XE2YKXRA.js +0 -544
- package/dist/chunks/chunk-XE2YKXRA.js.map +0 -7
package/dist/react.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CSSProperties } from 'react';
|
|
2
2
|
import type { XiaochunAudioOptions, XiaochunAudioSource, XiaochunAudioStream, XiaochunEvents, XiaochunInstance, XiaochunOptions } from './client';
|
|
3
|
-
import type { XcConfig, XcExpressionPayload, XcMotionPayload, XcStatePayload } from './protocol';
|
|
3
|
+
import type { XcConfig, XcExpressionPayload, XcMotionPayload, XcOutfitInfo, XcPrefetchedPayload, XcSceneInfo, XcStatePayload } from './protocol';
|
|
4
4
|
/** 事件回调 (全部可选)。引用变化不会重建实例 (内部用 ref 持有最新值)。 */
|
|
5
5
|
export interface XiaochunCallbacks {
|
|
6
6
|
/** xc.ready 握手成功。 */
|
|
@@ -12,8 +12,16 @@ export interface XiaochunCallbacks {
|
|
|
12
12
|
onStt?: (p: XiaochunEvents['stt']) => void;
|
|
13
13
|
onUtterance?: (p: XiaochunEvents['utterance']) => void;
|
|
14
14
|
onHitRegion?: (p: XiaochunEvents['hit-region']) => void;
|
|
15
|
+
/** 服装已生效 (含首次加载 initial:true)。 */
|
|
16
|
+
onOutfitChanged?: (p: XiaochunEvents['outfit-changed']) => void;
|
|
17
|
+
/** 场景已生效 (含握手后上报的当前场景 initial:true)。 */
|
|
18
|
+
onSceneChanged?: (p: XiaochunEvents['scene-changed']) => void;
|
|
15
19
|
onError?: (p: XiaochunEvents['error']) => void;
|
|
16
20
|
onDestroy?: () => void;
|
|
21
|
+
/** 用户拖动头像 (draggable): start / move / end, 位置已限幅 (视口坐标)。 */
|
|
22
|
+
onMove?: (p: XiaochunEvents['move']) => void;
|
|
23
|
+
/** 用户缩放头像 (resizable): start / move / end, 尺寸已限幅。想把尺寸同步进自己的状态, 在 phase === 'end' 时写回 width / height 即可 (不会重建 iframe)。 */
|
|
24
|
+
onResize?: (p: XiaochunEvents['resize']) => void;
|
|
17
25
|
}
|
|
18
26
|
export interface XiaochunHookOptions extends Omit<XiaochunOptions, 'container'>, XiaochunCallbacks {
|
|
19
27
|
/** 受控暂停: true = xc.pause, false = xc.resume。 */
|
|
@@ -50,6 +58,14 @@ export interface XiaochunHandle {
|
|
|
50
58
|
expression(name: XcExpressionPayload['name']): Promise<void>;
|
|
51
59
|
/** TODO: 协议已预留, /embed 暂未实现 → reject unsupported。 */
|
|
52
60
|
lookAt(x: number, y: number): Promise<void>;
|
|
61
|
+
/** 换内置服装 (串行 + last-wins, 见 XiaochunInstance.setOutfit)。 */
|
|
62
|
+
setOutfit(id: string): Promise<void>;
|
|
63
|
+
setScene(id: string): Promise<void>;
|
|
64
|
+
getOutfits(): Promise<XcOutfitInfo[]>;
|
|
65
|
+
getScenes(): Promise<XcSceneInfo[]>;
|
|
66
|
+
/** 预取服装资源到 iframe 的 IndexedDB; ids 省略 = 全部 (婚纱除外)。 */
|
|
67
|
+
prefetch(ids?: string[]): Promise<XcPrefetchedPayload>;
|
|
68
|
+
/** 旧命令; 任意 URL 需 allowCustomModel。内置服装请用 setOutfit。 */
|
|
53
69
|
setModel(m: string | {
|
|
54
70
|
url?: string;
|
|
55
71
|
outfit?: string;
|
package/dist/react.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
/*! @firetable/project-xiaochun v0.1.
|
|
1
|
+
/*! @firetable/project-xiaochun v0.1.15 | MIT | https://github.com/FireTable/project-xiaochun */
|
|
2
2
|
"use client";
|
|
3
3
|
import {
|
|
4
4
|
createXiaochun
|
|
5
|
-
} from "./chunks/chunk-
|
|
6
|
-
import
|
|
5
|
+
} from "./chunks/chunk-KQSKHPVF.js";
|
|
6
|
+
import {
|
|
7
|
+
isXcId
|
|
8
|
+
} from "./chunks/chunk-KKRZ3SUD.js";
|
|
7
9
|
|
|
8
10
|
// src/react.ts
|
|
9
11
|
import { createElement, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
@@ -15,7 +17,11 @@ var EVENT_MAP = [
|
|
|
15
17
|
["stt", "onStt"],
|
|
16
18
|
["utterance", "onUtterance"],
|
|
17
19
|
["hit-region", "onHitRegion"],
|
|
20
|
+
["outfit-changed", "onOutfitChanged"],
|
|
21
|
+
["scene-changed", "onSceneChanged"],
|
|
18
22
|
["error", "onError"],
|
|
23
|
+
["move", "onMove"],
|
|
24
|
+
["resize", "onResize"],
|
|
19
25
|
["destroy", "onDestroy"]
|
|
20
26
|
];
|
|
21
27
|
function useXiaochun(options = {}) {
|
|
@@ -30,8 +36,8 @@ function useXiaochun(options = {}) {
|
|
|
30
36
|
useEffect(() => {
|
|
31
37
|
if (!el) return;
|
|
32
38
|
const cur = live.current;
|
|
33
|
-
const { onHandshake: _a, onReady: _b, onProgress: _c, onState: _d, onStt: _e, onUtterance: _f, onHitRegion: _g, onError: _h, onDestroy: _i, paused: _p, mic: _m, allowedOrigins, ...create } = cur;
|
|
34
|
-
void [_a, _b, _c, _d, _e, _f, _g, _h, _i, _p, _m];
|
|
39
|
+
const { onHandshake: _a, onReady: _b, onProgress: _c, onState: _d, onStt: _e, onUtterance: _f, onHitRegion: _g, onError: _h, onDestroy: _i, onOutfitChanged: _j, onSceneChanged: _k, onMove: _q, onResize: _r, paused: _p, mic: _m, allowedOrigins, ...create } = cur;
|
|
40
|
+
void [_a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _p, _m, _q, _r];
|
|
35
41
|
const inst = createXiaochun({ ...create, allowedOrigins: allowedOrigins?.slice(), container: el });
|
|
36
42
|
const offs = [];
|
|
37
43
|
for (const [ev, cbName] of EVENT_MAP) {
|
|
@@ -61,20 +67,23 @@ function useXiaochun(options = {}) {
|
|
|
61
67
|
o.lazyMargin,
|
|
62
68
|
o.placeholder,
|
|
63
69
|
o.transparent,
|
|
64
|
-
o.width,
|
|
65
|
-
o.height,
|
|
66
70
|
o.position,
|
|
67
|
-
o.
|
|
68
|
-
o.ui,
|
|
71
|
+
JSON.stringify(o.ui ?? null),
|
|
69
72
|
o.heavy,
|
|
70
73
|
o.controls,
|
|
71
74
|
o.autoPause,
|
|
72
75
|
o.passthrough,
|
|
73
76
|
o.sandbox,
|
|
74
77
|
o.handshakeTimeout,
|
|
75
|
-
o.
|
|
78
|
+
o.crossOriginIsolated,
|
|
79
|
+
o.zIndex,
|
|
80
|
+
o.allowCustomModel,
|
|
81
|
+
o.persist,
|
|
82
|
+
Array.isArray(o.prefetch) ? o.prefetch.join(",") : o.prefetch
|
|
76
83
|
]);
|
|
77
|
-
const
|
|
84
|
+
const wantOutfit = o.outfit ?? o.model;
|
|
85
|
+
const first = useRef({ lang: o.lang, outfit: wantOutfit, scene: o.scene, width: o.width, height: o.height });
|
|
86
|
+
const resizableKey = JSON.stringify(o.resizable ?? false);
|
|
78
87
|
useEffect(() => {
|
|
79
88
|
if (!client || first.current.lang === o.lang) return;
|
|
80
89
|
first.current.lang = o.lang;
|
|
@@ -82,11 +91,32 @@ function useXiaochun(options = {}) {
|
|
|
82
91
|
});
|
|
83
92
|
}, [client, o.lang]);
|
|
84
93
|
useEffect(() => {
|
|
85
|
-
if (!client || first.current.
|
|
86
|
-
first.current.
|
|
87
|
-
if (
|
|
94
|
+
if (!client || first.current.outfit === wantOutfit) return;
|
|
95
|
+
first.current.outfit = wantOutfit;
|
|
96
|
+
if (wantOutfit) void (isXcId(wantOutfit) ? client.setOutfit(wantOutfit) : client.setModel(wantOutfit)).catch(() => {
|
|
97
|
+
});
|
|
98
|
+
}, [client, wantOutfit]);
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (!client || first.current.scene === o.scene) return;
|
|
101
|
+
first.current.scene = o.scene;
|
|
102
|
+
if (o.scene) void client.setScene(o.scene).catch(() => {
|
|
88
103
|
});
|
|
89
|
-
}, [client, o.
|
|
104
|
+
}, [client, o.scene]);
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (!client || first.current.width === o.width && first.current.height === o.height) return;
|
|
107
|
+
first.current.width = o.width;
|
|
108
|
+
first.current.height = o.height;
|
|
109
|
+
client.setSize(o.width ?? 320, o.height ?? 480);
|
|
110
|
+
}, [client, o.width, o.height]);
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
client?.setDraggable(o.draggable === true);
|
|
113
|
+
}, [client, o.draggable]);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
client?.setBorderRadius(o.borderRadius);
|
|
116
|
+
}, [client, o.borderRadius]);
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
client?.setResizable(o.resizable ?? false);
|
|
119
|
+
}, [client, resizableKey]);
|
|
90
120
|
useEffect(() => {
|
|
91
121
|
if (!client || o.paused === void 0) return;
|
|
92
122
|
if (o.paused) client.pause();
|
|
@@ -126,6 +156,11 @@ function XiaochunInner(props, ref) {
|
|
|
126
156
|
motion: (m) => c()?.motion(m) ?? reject(),
|
|
127
157
|
expression: (n) => c()?.expression(n) ?? reject(),
|
|
128
158
|
lookAt: (x, y) => c()?.lookAt(x, y) ?? reject(),
|
|
159
|
+
setOutfit: (id) => c()?.setOutfit(id) ?? reject(),
|
|
160
|
+
setScene: (id) => c()?.setScene(id) ?? reject(),
|
|
161
|
+
getOutfits: () => c()?.getOutfits() ?? reject(),
|
|
162
|
+
getScenes: () => c()?.getScenes() ?? reject(),
|
|
163
|
+
prefetch: (ids) => c()?.prefetch(ids) ?? reject(),
|
|
129
164
|
setModel: (m) => c()?.setModel(m) ?? reject(),
|
|
130
165
|
setConfig: (cfg) => c()?.setConfig(cfg) ?? reject(),
|
|
131
166
|
startListening: () => c()?.startListening() ?? reject(),
|
package/dist/react.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/react.ts"],
|
|
4
|
-
"sourcesContent": ["'use client';\n/**\n * react.ts \u2014 React \u5C01\u88C5 (\u5B50\u8DEF\u5F84\u5BFC\u51FA `@firetable/project-xiaochun/react`, react \u662F\u53EF\u9009 peerDependency)\u3002\n *\n * import { Xiaochun, useXiaochun } from '@firetable/project-xiaochun/react';\n * <Xiaochun ref={ref} width={320} height={480} transparent onReady={...} />\n * ref.current?.say('\u4F60\u597D') / ref.current?.speakAudio(arrayBuffer)\n *\n * \u8BBE\u8BA1\u8981\u70B9:\n * - \u517C\u5BB9 React 18 / 19 (\u53EA\u7528 forwardRef / useImperativeHandle / useEffect / useState / createElement, \u4E0D\u5199 JSX \u2192 \u65E0\u9700 jsx \u8FD0\u884C\u65F6)\u3002\n * - SSR / Next.js \u5B89\u5168: \u6587\u4EF6\u5934 'use client'; \u670D\u52A1\u7AEF\u53EA\u6E32\u67D3\u4E00\u4E2A**\u56FA\u5B9A\u5C3A\u5BF8**\u7684\u7A7A <div> (\u4E0D\u4EA7\u751F CLS), iframe \u53EA\u5728\u5BA2\u6237\u7AEF effect \u91CC\u521B\u5EFA\u3002\n * - StrictMode \u53CC\u6302\u8F7D\u4E0D\u6CC4\u6F0F: effect cleanup \u4E00\u5B9A destroy(); lazy \u6A21\u5F0F\u4E0B\u5047\u6302\u8F7D\u6839\u672C\u4E0D\u4F1A\u521B\u5EFA iframe\u3002\n * - \u4E0D\u6539\u53D8\u4E3B\u5165\u53E3\u4F53\u79EF: \u4E3B\u5165\u53E3 (index) \u4E0D\u5F15\u7528\u672C\u6587\u4EF6, \u4E0D\u7528 React \u7684\u4EBA\u4E0D\u4F1A\u591A\u62C9\u4E00\u4E2A\u5B57\u8282\u3002\n * - \u521B\u5EFA\u671F\u9009\u9879 (src/lazy/width/\u2026) \u53D8\u5316\u4F1A\u91CD\u5EFA\u5B9E\u4F8B; lang / model / paused / mic \u53D8\u5316\u5219\u70ED\u66F4\u65B0 (\u4E0D\u91CD\u5EFA iframe)\u3002\n */\nimport { createElement, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';\nimport type { CSSProperties, ForwardedRef, ReactElement } from 'react';\nimport { createXiaochun } from './client';\nimport type {\n XiaochunAudioOptions,\n XiaochunAudioSource,\n XiaochunAudioStream,\n XiaochunEvents,\n XiaochunInstance,\n XiaochunOptions,\n} from './client';\nimport type { XcConfig, XcExpressionPayload, XcMotionPayload, XcStatePayload } from './protocol';\n\n/** \u4E8B\u4EF6\u56DE\u8C03 (\u5168\u90E8\u53EF\u9009)\u3002\u5F15\u7528\u53D8\u5316\u4E0D\u4F1A\u91CD\u5EFA\u5B9E\u4F8B (\u5185\u90E8\u7528 ref \u6301\u6709\u6700\u65B0\u503C)\u3002 */\nexport interface XiaochunCallbacks {\n /** xc.ready \u63E1\u624B\u6210\u529F\u3002 */\n onHandshake?: (p: XiaochunEvents['handshake']) => void;\n /** \u6A21\u578B\u52A0\u8F7D\u5B8C\u6210 (= xc.loaded)\u3002 */\n onReady?: (p: XiaochunEvents['ready']) => void;\n onProgress?: (p: XiaochunEvents['progress']) => void;\n onState?: (p: XiaochunEvents['state']) => void;\n onStt?: (p: XiaochunEvents['stt']) => void;\n onUtterance?: (p: XiaochunEvents['utterance']) => void;\n onHitRegion?: (p: XiaochunEvents['hit-region']) => void;\n onError?: (p: XiaochunEvents['error']) => void;\n onDestroy?: () => void;\n}\n\nexport interface XiaochunHookOptions extends Omit<XiaochunOptions, 'container'>, XiaochunCallbacks {\n /** \u53D7\u63A7\u6682\u505C: true = xc.pause, false = xc.resume\u3002 */\n paused?: boolean;\n /** \u53D7\u63A7\u9EA6\u514B\u98CE: true = \u5F00\u59CB\u542C (\u9996\u6B21\u4F1A\u52A0\u8F7D STT), false = \u505C\u3002 */\n mic?: boolean;\n}\n\nexport interface UseXiaochunResult {\n /** \u7ED1\u5B9A\u5230\u4F60\u81EA\u5DF1\u7684\u5BB9\u5668: `<div ref={containerRef} style={{ width: 320, height: 480 }} />`\u3002\u56DE\u8C03 ref, React 18/19 \u901A\u7528\u3002 */\n containerRef: (el: HTMLElement | null) => void;\n /** \u5E95\u5C42 SDK \u5B9E\u4F8B; \u670D\u52A1\u7AEF / \u5C1A\u672A\u6302\u8F7D / \u5DF2\u5378\u8F7D\u65F6\u4E3A null\u3002 */\n client: XiaochunInstance | null;\n /** \u6A21\u578B\u662F\u5426\u52A0\u8F7D\u5B8C\u6210\u3002 */\n ready: boolean;\n /** \u6700\u8FD1\u4E00\u6B21 xc.state\u3002 */\n state: XcStatePayload | null;\n}\n\nconst EVENT_MAP: Array<[keyof XiaochunEvents, keyof XiaochunCallbacks]> = [\n ['handshake', 'onHandshake'],\n ['ready', 'onReady'],\n ['progress', 'onProgress'],\n ['state', 'onState'],\n ['stt', 'onStt'],\n ['utterance', 'onUtterance'],\n ['hit-region', 'onHitRegion'],\n ['error', 'onError'],\n ['destroy', 'onDestroy'],\n];\n\n/**\n * \u5728\u4F60\u81EA\u5DF1\u7684\u5BB9\u5668\u91CC\u521B\u5EFA / \u9500\u6BC1\u4E00\u4E2A\u5C0F\u8822\u5B9E\u4F8B (\u5BA2\u6237\u7AEF only)\u3002\n * \u9700\u8981\u5B8C\u5168\u81EA\u5B9A\u4E49\u5E03\u5C40\u65F6\u7528\u5B83; \u60F3\u7701\u4E8B\u7528 <Xiaochun />\u3002\n */\nexport function useXiaochun(options: XiaochunHookOptions = {}): UseXiaochunResult {\n const [el, setEl] = useState<HTMLElement | null>(null);\n const [client, setClient] = useState<XiaochunInstance | null>(null);\n const [ready, setReady] = useState(false);\n const [state, setState] = useState<XcStatePayload | null>(null);\n\n // \u56DE\u8C03\u4E0E\"\u70ED\u66F4\u65B0\u578B\"\u9009\u9879\u653E\u8FDB ref: \u5B83\u4EEC\u53D8\u5316\u4E0D\u5E94\u91CD\u5EFA iframe\n const live = useRef(options);\n live.current = options;\n\n const o = options;\n const originsKey = (o.allowedOrigins ?? []).join(',');\n\n useEffect(() => {\n if (!el) return;\n const cur = live.current;\n const { onHandshake: _a, onReady: _b, onProgress: _c, onState: _d, onStt: _e, onUtterance: _f, onHitRegion: _g, onError: _h, onDestroy: _i, paused: _p, mic: _m, allowedOrigins, ...create } = cur;\n void [_a, _b, _c, _d, _e, _f, _g, _h, _i, _p, _m];\n const inst = createXiaochun({ ...create, allowedOrigins: allowedOrigins?.slice(), container: el });\n const offs: Array<() => void> = [];\n for (const [ev, cbName] of EVENT_MAP) {\n offs.push(inst.on(ev, (p: unknown) => { (live.current[cbName] as ((x: unknown) => void) | undefined)?.(p); }));\n }\n offs.push(inst.on('ready', () => setReady(true)));\n offs.push(inst.on('state', (s) => setState(s)));\n if (cur.paused) inst.pause();\n if (cur.mic) void inst.ready.then(() => inst.mic(true)).catch(() => {});\n setClient(inst);\n return () => {\n // StrictMode \u7684\u5047\u5378\u8F7D / \u771F\u5378\u8F7D / \u91CD\u5EFA \u90FD\u8D70\u8FD9\u91CC: \u5FC5\u987B\u9500\u6BC1, \u5426\u5219 iframe \u4E0E window \u76D1\u542C\u6CC4\u6F0F\n offs.forEach((off) => off());\n inst.destroy();\n setClient(null);\n setReady(false);\n setState(null);\n };\n // \u53EA\u6709\"\u521B\u5EFA\u671F\u9009\u9879\"\u53D8\u5316\u624D\u91CD\u5EFA; lang/model/paused/mic/\u56DE\u8C03\u8D70\u4E0B\u9762\u7684\u70ED\u66F4\u65B0\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [el, o.src, o.origin, originsKey, o.lazy, o.lazyMargin, o.placeholder, o.transparent, o.width, o.height, o.position,\n o.draggable, o.ui, o.heavy, o.controls, o.autoPause, o.passthrough, o.sandbox, o.handshakeTimeout, o.zIndex]);\n\n // \u70ED\u66F4\u65B0: lang / model (\u8DF3\u8FC7\u9996\u6B21: \u521B\u5EFA\u65F6\u5DF2\u7ECF\u4F5C\u4E3A\u521D\u59CB\u9009\u9879\u4F20\u5165)\n const first = useRef({ lang: o.lang, model: o.model });\n useEffect(() => {\n if (!client || first.current.lang === o.lang) return;\n first.current.lang = o.lang;\n if (o.lang) void client.setConfig({ lang: o.lang }).catch(() => {});\n }, [client, o.lang]);\n useEffect(() => {\n if (!client || first.current.model === o.model) return;\n first.current.model = o.model;\n if (o.model) void client.setModel(o.model).catch(() => {});\n }, [client, o.model]);\n useEffect(() => {\n if (!client || o.paused === undefined) return;\n if (o.paused) client.pause(); else client.resume();\n }, [client, o.paused]);\n const micWanted = useRef(o.mic);\n useEffect(() => {\n if (!client || o.mic === undefined || micWanted.current === o.mic) { micWanted.current = o.mic; return; }\n micWanted.current = o.mic;\n void client.mic(!!o.mic).catch(() => {});\n }, [client, o.mic]);\n\n const containerRef = useCallback((node: HTMLElement | null) => setEl(node), []);\n return { containerRef, client, ready, state };\n}\n\n/** ref \u66B4\u9732\u7684\u547D\u4EE4\u5F0F\u53E5\u67C4 (\u5B9E\u4F8B\u5C1A\u672A\u521B\u5EFA / \u5DF2\u9500\u6BC1\u65F6, \u8FD4\u56DE Promise \u7684\u65B9\u6CD5\u4F1A reject)\u3002 */\nexport interface XiaochunHandle {\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 /** TODO: \u534F\u8BAE\u5DF2\u9884\u7559, /embed \u6682\u672A\u5B9E\u73B0 \u2192 reject unsupported\u3002 */\n lookAt(x: number, y: number): Promise<void>;\n setModel(m: string | { url?: string; outfit?: string; name?: string }): Promise<void>;\n setConfig(cfg: XcConfig): Promise<void>;\n startListening(): Promise<void>;\n stopListening(): Promise<void>;\n mic(enabled: boolean): Promise<void>;\n pause(): void;\n resume(): void;\n activate(): void;\n destroy(): void;\n /** \u6A21\u578B\u52A0\u8F7D\u5B8C\u6210\u65F6 resolve\u3002 */\n readonly ready: Promise<void>;\n /** \u5E95\u5C42 SDK \u5B9E\u4F8B (\u670D\u52A1\u7AEF / \u672A\u6302\u8F7D\u65F6 null)\u3002 */\n readonly instance: XiaochunInstance | null;\n}\n\nexport interface XiaochunProps extends XiaochunHookOptions {\n className?: string;\n /** \u5916\u5C42 div \u7684\u6837\u5F0F; width/height \u7531 props \u51B3\u5B9A (\u56FA\u5B9A\u5C3A\u5BF8, \u9632 CLS), \u8FD9\u91CC\u7684\u540C\u540D\u5B57\u6BB5\u4F1A\u88AB\u8986\u76D6\u3002 */\n style?: CSSProperties;\n}\n\nconst css = (v: number | string | undefined, d: string) => (v === undefined ? d : typeof v === 'number' ? `${v}px` : v);\nconst NOT_MOUNTED = () => new Error('[project-xiaochun] <Xiaochun /> is not mounted');\n\nfunction XiaochunInner(props: XiaochunProps, ref: ForwardedRef<XiaochunHandle>): ReactElement {\n const { className, style, ...hookOptions } = props;\n const { containerRef, client } = useXiaochun(hookOptions);\n const clientRef = useRef<XiaochunInstance | null>(null);\n clientRef.current = client;\n\n useImperativeHandle(ref, () => {\n const c = () => clientRef.current;\n const reject = <T,>(): Promise<T> => Promise.reject(NOT_MOUNTED());\n return {\n say: (t, o) => c()?.say(t, o) ?? reject(),\n speakAudio: (s, o) => c()?.speakAudio(s, o) ?? reject(),\n speakAudioStream: (o) => { const x = c(); if (!x) throw NOT_MOUNTED(); return x.speakAudioStream(o); },\n motion: (m) => c()?.motion(m) ?? reject(),\n expression: (n) => c()?.expression(n) ?? reject(),\n lookAt: (x, y) => c()?.lookAt(x, y) ?? reject(),\n setModel: (m) => c()?.setModel(m) ?? reject(),\n setConfig: (cfg) => c()?.setConfig(cfg) ?? reject(),\n startListening: () => c()?.startListening() ?? reject(),\n stopListening: () => c()?.stopListening() ?? reject(),\n mic: (e) => c()?.mic(e) ?? reject(),\n pause: () => c()?.pause(),\n resume: () => c()?.resume(),\n activate: () => c()?.activate(),\n destroy: () => c()?.destroy(),\n get ready() { return c()?.ready ?? reject<void>(); },\n get instance() { return c(); },\n };\n }, []);\n\n // \u670D\u52A1\u7AEF\u4E0E\u9996\u5E27\u5BA2\u6237\u7AEF\u6E32\u67D3\u5B8C\u5168\u4E00\u81F4 (hydration \u5B89\u5168): \u56FA\u5B9A\u5C3A\u5BF8\u7684\u7A7A div\n const box: CSSProperties = {\n ...style,\n width: css(props.width, '320px'),\n height: css(props.height, '480px'),\n maxWidth: '100%',\n position: style?.position ?? 'relative',\n };\n return createElement('div', { ref: containerRef, className, style: box, 'data-xiaochun-host': '' });\n}\n\n/** `<Xiaochun />`: \u5728\u5BA2\u6237\u7AEF\u521B\u5EFA\u5C0F\u8822 iframe; ref \u66B4\u9732 say / speakAudio / motion / \u2026 \u7B49\u547D\u4EE4\u5F0F\u65B9\u6CD5\u3002 */\nexport const Xiaochun = forwardRef<XiaochunHandle, XiaochunProps>(XiaochunInner);\nXiaochun.displayName = 'Xiaochun';\n\nexport type { XiaochunOptions, XiaochunInstance, XiaochunAudioOptions, XiaochunAudioSource, XiaochunAudioStream } from './client';\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["'use client';\n/**\n * react.ts \u2014 React \u5C01\u88C5 (\u5B50\u8DEF\u5F84\u5BFC\u51FA `@firetable/project-xiaochun/react`, react \u662F\u53EF\u9009 peerDependency)\u3002\n *\n * import { Xiaochun, useXiaochun } from '@firetable/project-xiaochun/react';\n * <Xiaochun ref={ref} width={320} height={480} transparent onReady={...} />\n * ref.current?.say('\u4F60\u597D') / ref.current?.speakAudio(arrayBuffer)\n *\n * \u8BBE\u8BA1\u8981\u70B9:\n * - \u517C\u5BB9 React 18 / 19 (\u53EA\u7528 forwardRef / useImperativeHandle / useEffect / useState / createElement, \u4E0D\u5199 JSX \u2192 \u65E0\u9700 jsx \u8FD0\u884C\u65F6)\u3002\n * - SSR / Next.js \u5B89\u5168: \u6587\u4EF6\u5934 'use client'; \u670D\u52A1\u7AEF\u53EA\u6E32\u67D3\u4E00\u4E2A**\u56FA\u5B9A\u5C3A\u5BF8**\u7684\u7A7A <div> (\u4E0D\u4EA7\u751F CLS), iframe \u53EA\u5728\u5BA2\u6237\u7AEF effect \u91CC\u521B\u5EFA\u3002\n * - StrictMode \u53CC\u6302\u8F7D\u4E0D\u6CC4\u6F0F: effect cleanup \u4E00\u5B9A destroy(); lazy \u6A21\u5F0F\u4E0B\u5047\u6302\u8F7D\u6839\u672C\u4E0D\u4F1A\u521B\u5EFA iframe\u3002\n * - \u4E0D\u6539\u53D8\u4E3B\u5165\u53E3\u4F53\u79EF: \u4E3B\u5165\u53E3 (index) \u4E0D\u5F15\u7528\u672C\u6587\u4EF6, \u4E0D\u7528 React \u7684\u4EBA\u4E0D\u4F1A\u591A\u62C9\u4E00\u4E2A\u5B57\u8282\u3002\n * - \u521B\u5EFA\u671F\u9009\u9879 (src/lazy/position/\u2026) \u53D8\u5316\u4F1A\u91CD\u5EFA\u5B9E\u4F8B; lang / outfit / scene / paused / mic / width / height / draggable / resizable / borderRadius \u53D8\u5316\u5219\u70ED\u66F4\u65B0\n * (\u4E0D\u91CD\u5EFA iframe, effect \u91CC\u8C03 setter)\u3002\u5C24\u5176 width / height: \u7528\u6237\u7F29\u653E (resizable) \u540E\u5BBF\u4E3B\u628A\u65B0\u5C3A\u5BF8\u5199\u56DE props \u4E5F\u4E0D\u4F1A\u91CD\u5EFA, \u6A21\u578B / \u52A8\u753B\u72B6\u6001\u90FD\u4FDD\u7559\u3002\n */\nimport { createElement, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';\nimport type { CSSProperties, ForwardedRef, ReactElement } from 'react';\nimport { createXiaochun } from './client';\nimport type {\n XiaochunAudioOptions,\n XiaochunAudioSource,\n XiaochunAudioStream,\n XiaochunEvents,\n XiaochunInstance,\n XiaochunOptions,\n} from './client';\nimport { isXcId } from './protocol';\nimport type { XcConfig, XcExpressionPayload, XcMotionPayload, XcOutfitInfo, XcPrefetchedPayload, XcSceneInfo, XcStatePayload } from './protocol';\n\n/** \u4E8B\u4EF6\u56DE\u8C03 (\u5168\u90E8\u53EF\u9009)\u3002\u5F15\u7528\u53D8\u5316\u4E0D\u4F1A\u91CD\u5EFA\u5B9E\u4F8B (\u5185\u90E8\u7528 ref \u6301\u6709\u6700\u65B0\u503C)\u3002 */\nexport interface XiaochunCallbacks {\n /** xc.ready \u63E1\u624B\u6210\u529F\u3002 */\n onHandshake?: (p: XiaochunEvents['handshake']) => void;\n /** \u6A21\u578B\u52A0\u8F7D\u5B8C\u6210 (= xc.loaded)\u3002 */\n onReady?: (p: XiaochunEvents['ready']) => void;\n onProgress?: (p: XiaochunEvents['progress']) => void;\n onState?: (p: XiaochunEvents['state']) => void;\n onStt?: (p: XiaochunEvents['stt']) => void;\n onUtterance?: (p: XiaochunEvents['utterance']) => void;\n onHitRegion?: (p: XiaochunEvents['hit-region']) => void;\n /** \u670D\u88C5\u5DF2\u751F\u6548 (\u542B\u9996\u6B21\u52A0\u8F7D initial:true)\u3002 */\n onOutfitChanged?: (p: XiaochunEvents['outfit-changed']) => void;\n /** \u573A\u666F\u5DF2\u751F\u6548 (\u542B\u63E1\u624B\u540E\u4E0A\u62A5\u7684\u5F53\u524D\u573A\u666F initial:true)\u3002 */\n onSceneChanged?: (p: XiaochunEvents['scene-changed']) => void;\n onError?: (p: XiaochunEvents['error']) => void;\n onDestroy?: () => void;\n /** \u7528\u6237\u62D6\u52A8\u5934\u50CF (draggable): start / move / end, \u4F4D\u7F6E\u5DF2\u9650\u5E45 (\u89C6\u53E3\u5750\u6807)\u3002 */\n onMove?: (p: XiaochunEvents['move']) => void;\n /** \u7528\u6237\u7F29\u653E\u5934\u50CF (resizable): start / move / end, \u5C3A\u5BF8\u5DF2\u9650\u5E45\u3002\u60F3\u628A\u5C3A\u5BF8\u540C\u6B65\u8FDB\u81EA\u5DF1\u7684\u72B6\u6001, \u5728 phase === 'end' \u65F6\u5199\u56DE width / height \u5373\u53EF (\u4E0D\u4F1A\u91CD\u5EFA iframe)\u3002 */\n onResize?: (p: XiaochunEvents['resize']) => void;\n}\n\nexport interface XiaochunHookOptions extends Omit<XiaochunOptions, 'container'>, XiaochunCallbacks {\n /** \u53D7\u63A7\u6682\u505C: true = xc.pause, false = xc.resume\u3002 */\n paused?: boolean;\n /** \u53D7\u63A7\u9EA6\u514B\u98CE: true = \u5F00\u59CB\u542C (\u9996\u6B21\u4F1A\u52A0\u8F7D STT), false = \u505C\u3002 */\n mic?: boolean;\n}\n\nexport interface UseXiaochunResult {\n /** \u7ED1\u5B9A\u5230\u4F60\u81EA\u5DF1\u7684\u5BB9\u5668: `<div ref={containerRef} style={{ width: 320, height: 480 }} />`\u3002\u56DE\u8C03 ref, React 18/19 \u901A\u7528\u3002 */\n containerRef: (el: HTMLElement | null) => void;\n /** \u5E95\u5C42 SDK \u5B9E\u4F8B; \u670D\u52A1\u7AEF / \u5C1A\u672A\u6302\u8F7D / \u5DF2\u5378\u8F7D\u65F6\u4E3A null\u3002 */\n client: XiaochunInstance | null;\n /** \u6A21\u578B\u662F\u5426\u52A0\u8F7D\u5B8C\u6210\u3002 */\n ready: boolean;\n /** \u6700\u8FD1\u4E00\u6B21 xc.state\u3002 */\n state: XcStatePayload | null;\n}\n\nconst EVENT_MAP: Array<[keyof XiaochunEvents, keyof XiaochunCallbacks]> = [\n ['handshake', 'onHandshake'],\n ['ready', 'onReady'],\n ['progress', 'onProgress'],\n ['state', 'onState'],\n ['stt', 'onStt'],\n ['utterance', 'onUtterance'],\n ['hit-region', 'onHitRegion'],\n ['outfit-changed', 'onOutfitChanged'],\n ['scene-changed', 'onSceneChanged'],\n ['error', 'onError'],\n ['move', 'onMove'],\n ['resize', 'onResize'],\n ['destroy', 'onDestroy'],\n];\n\n/**\n * \u5728\u4F60\u81EA\u5DF1\u7684\u5BB9\u5668\u91CC\u521B\u5EFA / \u9500\u6BC1\u4E00\u4E2A\u5C0F\u8822\u5B9E\u4F8B (\u5BA2\u6237\u7AEF only)\u3002\n * \u9700\u8981\u5B8C\u5168\u81EA\u5B9A\u4E49\u5E03\u5C40\u65F6\u7528\u5B83; \u60F3\u7701\u4E8B\u7528 <Xiaochun />\u3002\n */\nexport function useXiaochun(options: XiaochunHookOptions = {}): UseXiaochunResult {\n const [el, setEl] = useState<HTMLElement | null>(null);\n const [client, setClient] = useState<XiaochunInstance | null>(null);\n const [ready, setReady] = useState(false);\n const [state, setState] = useState<XcStatePayload | null>(null);\n\n // \u56DE\u8C03\u4E0E\"\u70ED\u66F4\u65B0\u578B\"\u9009\u9879\u653E\u8FDB ref: \u5B83\u4EEC\u53D8\u5316\u4E0D\u5E94\u91CD\u5EFA iframe\n const live = useRef(options);\n live.current = options;\n\n const o = options;\n const originsKey = (o.allowedOrigins ?? []).join(',');\n\n useEffect(() => {\n if (!el) return;\n const cur = live.current;\n const { onHandshake: _a, onReady: _b, onProgress: _c, onState: _d, onStt: _e, onUtterance: _f, onHitRegion: _g, onError: _h, onDestroy: _i, onOutfitChanged: _j, onSceneChanged: _k, onMove: _q, onResize: _r, paused: _p, mic: _m, allowedOrigins, ...create } = cur;\n void [_a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _p, _m, _q, _r];\n const inst = createXiaochun({ ...create, allowedOrigins: allowedOrigins?.slice(), container: el });\n const offs: Array<() => void> = [];\n for (const [ev, cbName] of EVENT_MAP) {\n offs.push(inst.on(ev, (p: unknown) => { (live.current[cbName] as ((x: unknown) => void) | undefined)?.(p); }));\n }\n offs.push(inst.on('ready', () => setReady(true)));\n offs.push(inst.on('state', (s) => setState(s)));\n if (cur.paused) inst.pause();\n if (cur.mic) void inst.ready.then(() => inst.mic(true)).catch(() => {});\n setClient(inst);\n return () => {\n // StrictMode \u7684\u5047\u5378\u8F7D / \u771F\u5378\u8F7D / \u91CD\u5EFA \u90FD\u8D70\u8FD9\u91CC: \u5FC5\u987B\u9500\u6BC1, \u5426\u5219 iframe \u4E0E window \u76D1\u542C\u6CC4\u6F0F\n offs.forEach((off) => off());\n inst.destroy();\n setClient(null);\n setReady(false);\n setState(null);\n };\n // \u53EA\u6709\"\u521B\u5EFA\u671F\u9009\u9879\"\u53D8\u5316\u624D\u91CD\u5EFA; lang/outfit/scene/paused/mic/\u56DE\u8C03\u8D70\u4E0B\u9762\u7684\u70ED\u66F4\u65B0\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [el, o.src, o.origin, originsKey, o.lazy, o.lazyMargin, o.placeholder, o.transparent, o.position,\n JSON.stringify(o.ui ?? null), o.heavy, o.controls, o.autoPause, o.passthrough, o.sandbox, o.handshakeTimeout, o.crossOriginIsolated, o.zIndex,\n o.allowCustomModel, o.persist, Array.isArray(o.prefetch) ? o.prefetch.join(',') : o.prefetch]);\n\n // \u70ED\u66F4\u65B0: lang / outfit (model \u662F\u65E7\u522B\u540D) / scene (\u8DF3\u8FC7\u9996\u6B21: \u521B\u5EFA\u65F6\u5DF2\u7ECF\u4F5C\u4E3A\u521D\u59CB\u9009\u9879\u4F20\u5165)\u3002\n // transparent \u662F\u521B\u5EFA\u671F\u9009\u9879 (\u53D8\u5316\u4F1A\u91CD\u5EFA); \u60F3\u4E0D\u91CD\u5EFA\u5730\u5207\u900F\u660E\u8BF7\u7528 scene\u3002\n const wantOutfit = o.outfit ?? o.model;\n const first = useRef({ lang: o.lang, outfit: wantOutfit, scene: o.scene, width: o.width, height: o.height });\n const resizableKey = JSON.stringify(o.resizable ?? false);\n useEffect(() => {\n if (!client || first.current.lang === o.lang) return;\n first.current.lang = o.lang;\n if (o.lang) void client.setConfig({ lang: o.lang }).catch(() => {});\n }, [client, o.lang]);\n useEffect(() => {\n if (!client || first.current.outfit === wantOutfit) return;\n first.current.outfit = wantOutfit;\n // busy / unknown_id \u4F1A\u8D70 onError, \u8FD9\u91CC\u4E0D\u91CD\u590D\u5904\u7406; \u65E7\u7528\u6CD5 model=\"https://\u2026\" \u4ECD\u8D70 setModel (\u53D7 allowCustomModel \u7EA6\u675F)\n if (wantOutfit) void (isXcId(wantOutfit) ? client.setOutfit(wantOutfit) : client.setModel(wantOutfit)).catch(() => {});\n }, [client, wantOutfit]);\n useEffect(() => {\n if (!client || first.current.scene === o.scene) return;\n first.current.scene = o.scene;\n if (o.scene) void client.setScene(o.scene).catch(() => {});\n }, [client, o.scene]);\n // \u5C3A\u5BF8 / \u624B\u52BF\u5F00\u5173: \u70ED\u66F4\u65B0, \u4E0D\u91CD\u5EFA iframe (\u7F29\u653E\u662F\u8FD0\u884C\u65F6\u72B6\u6001; \u53EA\u6709 props \u91CC\u7684\u503C\u771F\u7684\u53D8\u4E86\u624D\u8C03 setSize, \u7528\u6237\u7F29\u653E\u540E\u7684\u5C3A\u5BF8\u4E0D\u4F1A\u88AB\u65E0\u5173\u7684\u91CD\u6E32\u67D3\u8986\u76D6)\n useEffect(() => {\n if (!client || (first.current.width === o.width && first.current.height === o.height)) return;\n first.current.width = o.width; first.current.height = o.height;\n client.setSize(o.width ?? 320, o.height ?? 480);\n }, [client, o.width, o.height]);\n useEffect(() => { client?.setDraggable(o.draggable === true); }, [client, o.draggable]);\n useEffect(() => { client?.setBorderRadius(o.borderRadius); }, [client, o.borderRadius]);\n useEffect(() => { client?.setResizable(o.resizable ?? false); // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [client, resizableKey]);\n useEffect(() => {\n if (!client || o.paused === undefined) return;\n if (o.paused) client.pause(); else client.resume();\n }, [client, o.paused]);\n const micWanted = useRef(o.mic);\n useEffect(() => {\n if (!client || o.mic === undefined || micWanted.current === o.mic) { micWanted.current = o.mic; return; }\n micWanted.current = o.mic;\n void client.mic(!!o.mic).catch(() => {});\n }, [client, o.mic]);\n\n const containerRef = useCallback((node: HTMLElement | null) => setEl(node), []);\n return { containerRef, client, ready, state };\n}\n\n/** ref \u66B4\u9732\u7684\u547D\u4EE4\u5F0F\u53E5\u67C4 (\u5B9E\u4F8B\u5C1A\u672A\u521B\u5EFA / \u5DF2\u9500\u6BC1\u65F6, \u8FD4\u56DE Promise \u7684\u65B9\u6CD5\u4F1A reject)\u3002 */\nexport interface XiaochunHandle {\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 /** TODO: \u534F\u8BAE\u5DF2\u9884\u7559, /embed \u6682\u672A\u5B9E\u73B0 \u2192 reject unsupported\u3002 */\n lookAt(x: number, y: number): Promise<void>;\n /** \u6362\u5185\u7F6E\u670D\u88C5 (\u4E32\u884C + last-wins, \u89C1 XiaochunInstance.setOutfit)\u3002 */\n setOutfit(id: string): Promise<void>;\n setScene(id: string): Promise<void>;\n getOutfits(): Promise<XcOutfitInfo[]>;\n getScenes(): Promise<XcSceneInfo[]>;\n /** \u9884\u53D6\u670D\u88C5\u8D44\u6E90\u5230 iframe \u7684 IndexedDB; ids \u7701\u7565 = \u5168\u90E8 (\u5A5A\u7EB1\u9664\u5916)\u3002 */\n prefetch(ids?: string[]): Promise<XcPrefetchedPayload>;\n /** \u65E7\u547D\u4EE4; \u4EFB\u610F URL \u9700 allowCustomModel\u3002\u5185\u7F6E\u670D\u88C5\u8BF7\u7528 setOutfit\u3002 */\n setModel(m: string | { url?: string; outfit?: string; name?: string }): Promise<void>;\n setConfig(cfg: XcConfig): Promise<void>;\n startListening(): Promise<void>;\n stopListening(): Promise<void>;\n mic(enabled: boolean): Promise<void>;\n pause(): void;\n resume(): void;\n activate(): void;\n destroy(): void;\n /** \u6A21\u578B\u52A0\u8F7D\u5B8C\u6210\u65F6 resolve\u3002 */\n readonly ready: Promise<void>;\n /** \u5E95\u5C42 SDK \u5B9E\u4F8B (\u670D\u52A1\u7AEF / \u672A\u6302\u8F7D\u65F6 null)\u3002 */\n readonly instance: XiaochunInstance | null;\n}\n\nexport interface XiaochunProps extends XiaochunHookOptions {\n className?: string;\n /** \u5916\u5C42 div \u7684\u6837\u5F0F; width/height \u7531 props \u51B3\u5B9A (\u56FA\u5B9A\u5C3A\u5BF8, \u9632 CLS), \u8FD9\u91CC\u7684\u540C\u540D\u5B57\u6BB5\u4F1A\u88AB\u8986\u76D6\u3002 */\n style?: CSSProperties;\n}\n\nconst css = (v: number | string | undefined, d: string) => (v === undefined ? d : typeof v === 'number' ? `${v}px` : v);\nconst NOT_MOUNTED = () => new Error('[project-xiaochun] <Xiaochun /> is not mounted');\n\nfunction XiaochunInner(props: XiaochunProps, ref: ForwardedRef<XiaochunHandle>): ReactElement {\n const { className, style, ...hookOptions } = props;\n const { containerRef, client } = useXiaochun(hookOptions);\n const clientRef = useRef<XiaochunInstance | null>(null);\n clientRef.current = client;\n\n useImperativeHandle(ref, () => {\n const c = () => clientRef.current;\n const reject = <T,>(): Promise<T> => Promise.reject(NOT_MOUNTED());\n return {\n say: (t, o) => c()?.say(t, o) ?? reject(),\n speakAudio: (s, o) => c()?.speakAudio(s, o) ?? reject(),\n speakAudioStream: (o) => { const x = c(); if (!x) throw NOT_MOUNTED(); return x.speakAudioStream(o); },\n motion: (m) => c()?.motion(m) ?? reject(),\n expression: (n) => c()?.expression(n) ?? reject(),\n lookAt: (x, y) => c()?.lookAt(x, y) ?? reject(),\n setOutfit: (id) => c()?.setOutfit(id) ?? reject(),\n setScene: (id) => c()?.setScene(id) ?? reject(),\n getOutfits: () => c()?.getOutfits() ?? reject(),\n getScenes: () => c()?.getScenes() ?? reject(),\n prefetch: (ids) => c()?.prefetch(ids) ?? reject(),\n setModel: (m) => c()?.setModel(m) ?? reject(),\n setConfig: (cfg) => c()?.setConfig(cfg) ?? reject(),\n startListening: () => c()?.startListening() ?? reject(),\n stopListening: () => c()?.stopListening() ?? reject(),\n mic: (e) => c()?.mic(e) ?? reject(),\n pause: () => c()?.pause(),\n resume: () => c()?.resume(),\n activate: () => c()?.activate(),\n destroy: () => c()?.destroy(),\n get ready() { return c()?.ready ?? reject<void>(); },\n get instance() { return c(); },\n };\n }, []);\n\n // \u670D\u52A1\u7AEF\u4E0E\u9996\u5E27\u5BA2\u6237\u7AEF\u6E32\u67D3\u5B8C\u5168\u4E00\u81F4 (hydration \u5B89\u5168): \u56FA\u5B9A\u5C3A\u5BF8\u7684\u7A7A div\n const box: CSSProperties = {\n ...style,\n width: css(props.width, '320px'),\n height: css(props.height, '480px'),\n maxWidth: '100%',\n position: style?.position ?? 'relative',\n };\n return createElement('div', { ref: containerRef, className, style: box, 'data-xiaochun-host': '' });\n}\n\n/** `<Xiaochun />`: \u5728\u5BA2\u6237\u7AEF\u521B\u5EFA\u5C0F\u8822 iframe; ref \u66B4\u9732 say / speakAudio / motion / \u2026 \u7B49\u547D\u4EE4\u5F0F\u65B9\u6CD5\u3002 */\nexport const Xiaochun = forwardRef<XiaochunHandle, XiaochunProps>(XiaochunInner);\nXiaochun.displayName = 'Xiaochun';\n\nexport type { XiaochunOptions, XiaochunInstance, XiaochunAudioOptions, XiaochunAudioSource, XiaochunAudioStream } from './client';\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firetable/project-xiaochun",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Embed Project XiaoChun (3D anime companion) into any web page via a lazy, origin-checked iframe. Zero-dependency SDK + <xiaochun-avatar> web component.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "FireTable",
|
|
@@ -77,8 +77,10 @@
|
|
|
77
77
|
"build": "node ./build.mjs",
|
|
78
78
|
"typecheck": "tsc --noEmit",
|
|
79
79
|
"prepack": "node ./build.mjs",
|
|
80
|
-
"test": "node --test test/smoke.test.mjs test/react-ssr.test.mjs",
|
|
81
|
-
"test:browser": "node test/browser/react-harness.mjs && node test/browser/style-harness.mjs"
|
|
80
|
+
"test": "node --test test/smoke.test.mjs test/react-ssr.test.mjs test/outfit.test.mjs test/gesture.test.mjs",
|
|
81
|
+
"test:browser": "node test/browser/react-harness.mjs && node test/browser/style-harness.mjs",
|
|
82
|
+
"test:outfit": "node test/browser/outfit-harness.mjs",
|
|
83
|
+
"test:e2e": "node test/browser/embed-e2e.mjs"
|
|
82
84
|
},
|
|
83
85
|
"devDependencies": {
|
|
84
86
|
"@types/react": "^19.0.0",
|
|
@@ -1,7 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
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.setConfig',\n 'xc.mic',\n 'xc.pause',\n 'xc.resume',\n 'xc.destroy',\n] as const;\n\n/** iframe \u2192 \u5BBF\u4E3B \u4E8B\u4EF6\u3002 */\nexport const XC_FRAME_TO_HOST = [\n 'xc.ready',\n 'xc.load.progress',\n 'xc.loaded',\n 'xc.state',\n 'xc.stt',\n 'xc.utterance',\n 'xc.hit-region',\n 'xc.error',\n] as const;\n\nexport type XcHostMessageType = (typeof XC_HOST_TO_FRAME)[number];\nexport type XcFrameMessageType = (typeof XC_FRAME_TO_HOST)[number];\n\n/** \u5F53\u524D /embed \u5DF2\u5B9E\u73B0\u7684\u547D\u4EE4 (xc.init \u662F\u63E1\u624B, \u4E0D\u8BA1\u5165)\u3002 */\nexport const XC_IMPLEMENTED_COMMANDS = [\n 'xc.say',\n 'xc.audio',\n 'xc.audio.chunk',\n 'xc.audio.end',\n 'xc.motion',\n 'xc.expression',\n 'xc.pointer',\n 'xc.setModel',\n 'xc.setConfig',\n 'xc.mic',\n 'xc.pause',\n 'xc.resume',\n 'xc.destroy',\n] as const satisfies readonly XcHostMessageType[];\n\n/**\n * \u6682\u4E0D\u652F\u6301 (\u5E95\u5C42\u65E0\u5BF9\u5E94\u80FD\u529B), /embed \u56DE `xc.error{code:'unsupported'}`\u3002\n * TODO(xc.lookAt): \u89C6\u7EBF\u76EE\u524D\u7531\u76F8\u673A + \u968F\u673A\u626B\u89C6\u9A71\u52A8 (GazeController), \u6CA1\u6709\"\u5916\u90E8\u6307\u5B9A\u6CE8\u89C6\u70B9\"\u7684\u5165\u53E3;\n * \u9700\u8981\u5148\u5728 gaze \u5C42\u52A0 overrideTarget \u518D\u5F00\u653E\u3002\n */\nexport const XC_UNSUPPORTED_COMMANDS = ['xc.lookAt'] as const satisfies readonly XcHostMessageType[];\n\n/**\n * xc.* \u2194 xiaochun:// \u5BF9\u7167\u8868 (\u540C\u4E00\u8BED\u4E49\u7684\u4E24\u4E2A\u4F20\u8F93\u5C42, \u4E3B\u5E94\u7528\u5185\u8D70\u540C\u4E00\u4E2A handler: src/core/protocol/handler.ts)\u3002\n * - `action`: \u5185\u90E8 protocol action \u5BF9\u8C61 (src/core/protocol/types.ts); null = \u6CA1\u6709 deep link \u5BF9\u5E94\u7269\u3002\n * - `url`: \u5BF9\u5E94\u7684 deep link \u5F62\u5F0F (OS \u7EA7, \u53EA\u5728 Tauri \u58F3\u751F\u6548; iframe \u4E0D\u4F1A\u54CD\u5E94 xiaochun://)\u3002\n */\nexport const XC_PROTOCOL_MAPPING = [\n { xc: 'xc.say', note: 'mode:\"speak\" (\u9ED8\u8BA4)', action: 'speak', url: 'xiaochun://speak?text=\u2026' },\n { xc: 'xc.say', note: 'mode:\"chat\" (\u8D70 LLM)', action: null, url: null },\n { xc: 'xc.audio', note: 'source \u4E3A URL \u5B57\u7B26\u4E32', action: 'speak', url: 'xiaochun://speak?audioUrl=\u2026[&text=\u2026]' },\n { xc: 'xc.audio', note: 'source \u4E3A ArrayBuffer / Blob', action: 'audio', url: null },\n { xc: 'xc.audio.chunk / xc.audio.end', note: '\u6D41\u5F0F PCM', action: 'audio', url: null },\n] as const;\n\nexport type XcErrorCode =\n | 'unsupported' // \u547D\u4EE4/\u53C2\u6570\u5B58\u5728\u4F46\u5E95\u5C42\u6682\u65E0\u80FD\u529B\n | 'bad_request' // \u53C2\u6570\u7F3A\u5931/\u975E\u6CD5\n | 'not_ready' // \u6A21\u578B\u5C1A\u672A\u52A0\u8F7D\u5B8C\u6210\n | 'origin_denied' // \u63E1\u624B\u6765\u6E90\u4E0D\u5728\u767D\u540D\u5355\n | 'failed'; // \u6267\u884C\u671F\u5F02\u5E38\n\nexport type XcLang = 'zh-CN' | 'en' | 'ja';\nexport type XcPhase = 'idle' | 'loading' | 'thinking' | 'speaking' | 'listening' | 'paused';\n/** lazy = \u4E0D\u9884\u70ED WebLLM / EMAGE, \u9996\u6B21\u4E92\u52A8\u518D\u52A0\u8F7D (\u9ED8\u8BA4); eager = \u7ACB\u5373\u9884\u70ED\u3002 */\nexport type XcHeavyMode = 'lazy' | 'eager';\n\nexport interface XcEnvelope<T extends string = string, P = unknown> {\n type: T;\n v: typeof XC_PROTOCOL_VERSION;\n /** \u5BBF\u4E3B\u547D\u4EE4\u53EF\u5E26 id; \u5BF9\u5E94\u7684 xc.error / xc.utterance \u4F1A\u56DE\u5E26\u540C\u4E00\u4E2A id\u3002 */\n id?: string;\n payload: P;\n}\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u5BBF\u4E3B \u2192 iframe payload \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nexport interface XcConfig {\n lang?: XcLang;\n /** \u80CC\u666F\u900F\u660E (\u53E0\u5728\u5BBF\u4E3B\u9875\u9762\u4E0A)\u3002 */\n transparent?: boolean;\n /** \u662F\u5426\u663E\u793A iframe \u5185\u7F6E UI (ChatBar)\u3002\u9ED8\u8BA4 false\u3002 */\n ui?: boolean;\n /** \u91CD\u8D44\u6E90\u52A0\u8F7D\u7B56\u7565, \u89C1 XcHeavyMode\u3002 */\n heavy?: XcHeavyMode;\n}\n\nexport interface XcInitPayload {\n /** \u5BBF\u4E3B\u58F0\u660E\u7684\u81EA\u8EAB origin (iframe \u4F1A\u4E0E event.origin \u4EA4\u53C9\u6821\u9A8C)\u3002 */\n hostOrigin: string;\n config?: XcConfig;\n}\n\nexport interface XcSayPayload {\n text: string;\n /**\n * 'speak' (\u9ED8\u8BA4): \u76F4\u63A5 TTS + \u52A8\u4F5C, \u4E0D\u7ECF\u8FC7 LLM\u3002\n * 'chat': \u5F53\u4F5C\u7528\u6237\u8F93\u5165\u8D70 WebLLM / \u81EA\u5B9A\u4E49 provider (\u4F1A\u89E6\u53D1\u5927\u6A21\u578B\u52A0\u8F7D)\u3002\n */\n mode?: 'speak' | 'chat';\n}\n\n/** \u5BBF\u4E3B\u97F3\u9891\u7684\u7F16\u7801: encoded = \u5BB9\u5668\u683C\u5F0F (mp3/wav/ogg/aac/webm\u2026 \u7531\u6D4F\u89C8\u5668 decodeAudioData \u51B3\u5B9A); pcm16 / float32 = \u65E0\u5934\u539F\u59CB PCM (\u5C0F\u7AEF, \u4EA4\u9519)\u3002 */\nexport type XcAudioFormat = 'encoded' | 'pcm16' | 'float32';\n\nexport interface XcAudioOptions {\n /** \u6C14\u6CE1\u91CC\u663E\u793A\u7684\u6587\u5B57 (\u53EF\u9009; \u4E0D\u5F71\u54CD\u97F3\u9891\u4E0E\u52A8\u4F5C)\u3002 */\n text?: string;\n /** \u662F\u5426\u7531 EMAGE \u6839\u636E\u8FD9\u6BB5\u97F3\u9891\u751F\u6210\u5168\u8EAB\u52A8\u4F5C, \u9ED8\u8BA4 true\u3002false = \u53EA\u64AD\u653E\u97F3\u9891 (\u6B64\u65F6\u5B8C\u5168\u4E0D\u52A0\u8F7D EMAGE)\u3002 */\n motion?: boolean;\n /** \u662F\u5426\u9A71\u52A8\u53E3\u578B (\u97F3\u91CF RMS \u2192 'aa'), \u9ED8\u8BA4 true\u3002 */\n lipsync?: boolean;\n}\n\n/**\n * xc.audio \u2014 \u6574\u6BB5\u97F3\u9891: iframe \u5185\u89E3\u7801 \u2192 16 kHz \u2192 EMAGE \u7A97\u53E3\u63A8\u7406 + \u540C\u4E00\u6BB5\u97F3\u9891\u64AD\u653E + \u53E3\u578B, \u64AD\u5B8C\u56DE xc.utterance{phase:'end'}\u3002\n * \u4FE1\u5C01 `id` \u7528\u4E8E\u5173\u8054 xc.utterance / xc.error\u3002\u5927 ArrayBuffer \u8BF7\u653E\u8FDB postMessage \u7684 transfer \u5217\u8868 (SDK \u9ED8\u8BA4\u8FD9\u4E48\u505A)\u3002\n */\nexport interface XcAudioPayload extends XcAudioOptions {\n /** ArrayBuffer / Blob (structured clone) \u6216 URL \u5B57\u7B26\u4E32 (iframe \u5185 fetch, \u4EC5 https \u6216\u540C\u6E90, \u9700 CORS)\u3002 */\n source: ArrayBuffer | Blob | string;\n /** MIME \u63D0\u793A, \u5982 'audio/mpeg'\u3002\u4EC5\u4F5C\u4FE1\u606F, \u89E3\u7801\u4EE5\u6D4F\u89C8\u5668\u55C5\u63A2\u4E3A\u51C6\u3002 */\n mimeType?: string;\n /** \u9ED8\u8BA4 'encoded'\u3002 */\n format?: XcAudioFormat;\n /** \u4EC5\u539F\u59CB PCM \u9700\u8981 (\u9ED8\u8BA4 16000); \u5BB9\u5668\u683C\u5F0F\u5FFD\u7565 (\u4EE5\u6587\u4EF6\u5185\u7684\u91C7\u6837\u7387\u4E3A\u51C6)\u3002\u8303\u56F4 8000~96000\u3002 */\n sampleRate?: number;\n /** \u539F\u59CB PCM \u7684\u58F0\u9053\u6570 (\u4EA4\u9519), 1 \u6216 2, \u9ED8\u8BA4 1; \u5185\u90E8\u4E0B\u6DF7\u4E3A\u5355\u58F0\u9053\u3002 */\n channels?: 1 | 2;\n}\n\n/**\n * xc.audio.chunk \u2014 \u6D41\u5F0F\u97F3\u9891\u5206\u5757 (\u539F\u59CB PCM)\u3002\u4FE1\u5C01 `id` = \u6D41 id: \u540C\u4E00\u4E2A id \u7684\u7B2C\u4E00\u4E2A chunk \u5F00\u542F\u4E00\u6B21\u8BF4\u8BDD (\u56DE xc.utterance start),\n * \u4E4B\u540E\u7684 chunk \u8FFD\u52A0; \u4EE5 xc.audio.end \u6536\u5C3E\u3002\u6BCF\u4E2A chunk \u7684 `data` \u5E94\u653E\u8FDB transfer \u5217\u8868\u3002\n * \u9009\u9879 (text/motion/lipsync) \u53EA\u5728\u7B2C\u4E00\u4E2A chunk \u91CC\u751F\u6548; sampleRate/format \u6574\u6761\u6D41\u5FC5\u987B\u4E00\u81F4\u3002\n */\nexport interface XcAudioChunkPayload extends XcAudioOptions {\n data: ArrayBuffer;\n format: Exclude<XcAudioFormat, 'encoded'>;\n /** 8000~96000\u3002 */\n sampleRate: number;\n channels?: 1 | 2;\n}\n\nexport interface XcAudioEndPayload {\n /** true = \u7ACB\u5373\u6253\u65AD (\u4E22\u5F03\u672A\u64AD\u653E\u7684\u97F3\u9891); \u9ED8\u8BA4 false = \u64AD\u5B8C\u5DF2\u6536\u5230\u7684\u97F3\u9891\u540E\u7ED3\u675F\u3002 */\n abort?: boolean;\n}\n\nexport interface XcMotionPayload {\n /** .vrma URL (https:// \u6216\u540C\u6E90\u8DEF\u5F84)\u3002\u4E0E stop \u4E8C\u9009\u4E00\u3002 */\n url?: string;\n /** \u5185\u7F6E\u52A8\u4F5C\u540D, \u76EE\u524D\u4EC5 'thinking'\u3002 */\n name?: 'thinking';\n stop?: boolean;\n loop?: boolean;\n /** \u6DE1\u5165\u6DE1\u51FA\u79D2\u6570, \u8303\u56F4 0.26~3, \u8C03\u5927\u66F4\u67D4\u548C\u4F46\u54CD\u5E94\u66F4\u6162\u3002 */\n fadeDuration?: number;\n /** \u64AD\u653E\u500D\u901F, \u8303\u56F4 0.25~3\u3002 */\n timeScale?: number;\n mask?: 'all' | 'upperBody';\n}\n\nexport interface XcExpressionPayload {\n name: 'neutral' | 'happy' | 'angry' | 'sad' | 'relaxed' | 'surprised';\n}\n\n/** xc.lookAt \u2014 TODO: \u6682\u4E0D\u652F\u6301, \u6536\u5230\u4F1A\u8FD4\u56DE unsupported\u3002 */\nexport interface XcLookAtPayload {\n /** \u5F52\u4E00\u5316 (-1..1), \u76F8\u5BF9 iframe \u4E2D\u5FC3\u3002 */\n x: number;\n y: number;\n}\n\nexport interface XcPointerPayload {\n /** iframe \u5185 client \u5750\u6807 (px)\u3002SDK \u4F1A\u628A\u5BBF\u4E3B pointer \u4E8B\u4EF6\u6362\u7B97\u597D\u518D\u53D1\u3002 */\n x: number;\n y: number;\n}\n\nexport interface XcSetModelPayload {\n /** \u5B8C\u6574 .vrm / .vrmaddon / .vrmbase URL (\u9700 CORS)\u3002 */\n url?: string;\n /** \u5185\u7F6E\u670D\u88C5 key, \u89C1\u4E3B\u4ED3\u5E93 APP_CONFIG.model.addons, \u5982 'xiaochun_maid'\u3002 */\n outfit?: string;\n name?: string;\n}\n\nexport interface XcMicPayload {\n enabled: boolean;\n}\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 iframe \u2192 \u5BBF\u4E3B payload \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nexport interface XcReadyPayload {\n version: string;\n protocol: typeof XC_PROTOCOL_VERSION;\n capabilities: {\n commands: string[];\n unsupported: string[];\n stt: boolean;\n transparent: boolean;\n /** \u5BBF\u4E3B\u97F3\u9891\u80FD\u529B (xc.audio / xc.audio.chunk)\u3002\u65E7\u7248 /embed \u6CA1\u6709\u8FD9\u4E2A\u5B57\u6BB5\u3002 */\n audio?: { formats: XcAudioFormat[]; streaming: boolean; maxSeconds: number };\n };\n}\n\nexport interface XcLoadProgressPayload {\n phase: 'model';\n /** 0~100 */\n progress: number;\n}\n\nexport interface XcLoadedPayload {\n model: string;\n}\n\nexport interface XcStatePayload {\n phase: XcPhase;\n paused: boolean;\n heavy: XcHeavyMode;\n}\n\nexport type XcSttPayload =\n | { kind: 'state'; state: 'idle' | 'loading' | 'listening' | 'recognizing' | 'error' }\n | { kind: 'progress'; percent: number }\n | { kind: 'text'; text: string };\n\nexport interface XcUtterancePayload {\n phase: 'start' | 'end';\n text: string;\n /** 'text' = xc.say (TTS); 'audio' = xc.audio / xc.audio.chunk (\u5BBF\u4E3B\u97F3\u9891, \u6CA1\u6709\u8D70 TTS)\u3002\u65E7\u7248 /embed \u6CA1\u6709\u8FD9\u4E2A\u5B57\u6BB5\u3002 */\n kind?: 'text' | 'audio';\n}\n\nexport interface XcHitRegionPayload {\n hit: boolean;\n x: number;\n y: number;\n}\n\nexport interface XcErrorPayload {\n code: XcErrorCode;\n message: string;\n /** \u89E6\u53D1\u9519\u8BEF\u7684\u547D\u4EE4\u540D (\u5982 'xc.lookAt')\u3002 */\n command?: string;\n}\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u7C7B\u578B\u6620\u5C04 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nexport interface XcHostPayloadMap {\n 'xc.init': XcInitPayload;\n 'xc.say': XcSayPayload;\n 'xc.audio': XcAudioPayload;\n 'xc.audio.chunk': XcAudioChunkPayload;\n 'xc.audio.end': XcAudioEndPayload;\n 'xc.motion': XcMotionPayload;\n 'xc.expression': XcExpressionPayload;\n 'xc.lookAt': XcLookAtPayload;\n 'xc.pointer': XcPointerPayload;\n 'xc.setModel': XcSetModelPayload;\n 'xc.setConfig': XcConfig;\n 'xc.mic': XcMicPayload;\n 'xc.pause': undefined;\n 'xc.resume': undefined;\n 'xc.destroy': undefined;\n}\n\nexport interface XcFramePayloadMap {\n 'xc.ready': XcReadyPayload;\n 'xc.load.progress': XcLoadProgressPayload;\n 'xc.loaded': XcLoadedPayload;\n 'xc.state': XcStatePayload;\n 'xc.stt': XcSttPayload;\n 'xc.utterance': XcUtterancePayload;\n 'xc.hit-region': XcHitRegionPayload;\n 'xc.error': XcErrorPayload;\n}\n\nexport type XcHostMessage = {\n [K in XcHostMessageType]: XcEnvelope<K, XcHostPayloadMap[K]>;\n}[XcHostMessageType];\n\nexport type XcFrameMessage = {\n [K in XcFrameMessageType]: XcEnvelope<K, XcFramePayloadMap[K]>;\n}[XcFrameMessageType];\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u5C0F\u5DE5\u5177 (SDK \u4E0E /embed \u5171\u7528) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nexport function xcMessage<K extends XcHostMessageType | XcFrameMessageType>(\n type: K,\n payload?: unknown,\n id?: string,\n): XcEnvelope<K, any> {\n const m: XcEnvelope<K, any> = { type, v: XC_PROTOCOL_VERSION, payload };\n if (id !== undefined) m.id = id;\n return m;\n}\n\n/** \u662F\u5426\u4E3A\u5408\u6CD5 xc \u4FE1\u5C01 (\u53EA\u68C0\u67E5\u7ED3\u6784, \u4E0D\u68C0\u67E5 payload)\u3002 */\nexport function isXcEnvelope(data: unknown): data is XcEnvelope {\n if (!data || typeof data !== 'object') return false;\n const d = data as Record<string, unknown>;\n return typeof d.type === 'string' && d.type.startsWith('xc.') && d.v === XC_PROTOCOL_VERSION;\n}\n\n/**\n * \u628A\u7528\u6237\u7ED9\u7684 origin \u89C4\u6574\u6210\u4E25\u683C\u7684 `scheme://host[:port]`\u3002\n * \u62D2\u7EDD '*' / \u901A\u914D / \u975E http(s) / \u5E26\u8DEF\u5F84\u7684\u5B57\u7B26\u4E32 \u2192 \u8FD4\u56DE null\u3002\n */\nexport function normalizeOrigin(input: string | null | undefined): string | null {\n if (!input || input === '*' || input === 'null') return null;\n try {\n const u = new URL(input);\n if (u.protocol !== 'https:' && u.protocol !== 'http:') return null;\n return u.origin;\n } catch {\n return null;\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;AAaO,IAAM,sBAAsB;AAG5B,IAAM,oBAAoB;AAC1B,IAAM,iBAAiB,GAAG,iBAAiB;AAG3C,IAAM,mBAAmB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,mBAAmB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAMO,IAAM,0BAA0B;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,IAAM,0BAA0B,CAAC,WAAW;AAO5C,IAAM,sBAAsB;AAAA,EACjC,EAAE,IAAI,UAAU,MAAM,+BAAqB,QAAQ,SAAS,KAAK,+BAA0B;AAAA,EAC3F,EAAE,IAAI,UAAU,MAAM,4BAAuB,QAAQ,MAAM,KAAK,KAAK;AAAA,EACrE,EAAE,IAAI,YAAY,MAAM,wCAAoB,QAAQ,SAAS,KAAK,iDAAuC;AAAA,EACzG,EAAE,IAAI,YAAY,MAAM,oCAA+B,QAAQ,SAAS,KAAK,KAAK;AAAA,EAClF,EAAE,IAAI,iCAAiC,MAAM,oBAAU,QAAQ,SAAS,KAAK,KAAK;AACpF;AA4OO,SAAS,UACd,MACA,SACA,IACoB;AACpB,QAAM,IAAwB,EAAE,MAAM,GAAG,qBAAqB,QAAQ;AACtE,MAAI,OAAO,OAAW,GAAE,KAAK;AAC7B,SAAO;AACT;AAGO,SAAS,aAAa,MAAmC;AAC9D,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,QAAM,IAAI;AACV,SAAO,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,WAAW,KAAK,KAAK,EAAE,MAAM;AAC3E;AAMO,SAAS,gBAAgB,OAAiD;AAC/E,MAAI,CAAC,SAAS,UAAU,OAAO,UAAU,OAAQ,QAAO;AACxD,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,KAAK;AACvB,QAAI,EAAE,aAAa,YAAY,EAAE,aAAa,QAAS,QAAO;AAC9D,WAAO,EAAE;AAAA,EACX,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|