@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/dist/client.d.ts CHANGED
@@ -7,7 +7,9 @@
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 XcReadyPayload, type XcSttPayload, type XcStatePayload, type XcUtterancePayload } from './protocol';
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';
11
+ import { type XiaochunResizeLimits } from './gesture-box';
12
+ export type { XiaochunResizeLimits } from './gesture-box';
11
13
  export type XiaochunPosition = 'inline' | 'bottom-right' | 'bottom-left';
12
14
  /** lazy: false=立即创建; true/'idle'=进入视口且空闲后创建; 'click'=仅点击占位或调用 API 时创建。 */
13
15
  export type XiaochunLazy = boolean | 'idle' | 'click';
@@ -25,20 +27,81 @@ export interface XiaochunOptions {
25
27
  lazyMargin?: number;
26
28
  /** 占位图 URL / 元素; false = 不要占位。 */
27
29
  placeholder?: string | HTMLElement | false;
30
+ /**
31
+ * 背景透明 (叠在宿主页上)。等价于 `scene: 'transparent'`; 同时给了 `scene` 或已保存的偏好时, 以它们为准。
32
+ * 运行中想切换请用 `setScene()`: SDK 会同步外壳背景和穿透。
33
+ */
28
34
  transparent?: boolean;
35
+ /** 初始场景: 'light' | 'dark' | 'transparent' (以 getScenes() 为准)。省略 = 跟随 transparent 选项 / 系统亮暗。 */
36
+ scene?: XcSceneId | (string & {});
29
37
  /** 固定尺寸, 数字=px, 字符串=CSS 长度。务必给定, 否则无法预留空间 (CLS)。默认 320x480。 */
30
38
  width?: number | string;
31
39
  height?: number | string;
32
40
  position?: XiaochunPosition;
33
- /** 悬浮模式下允许拖动 (左上角手柄)。 */
41
+ /**
42
+ * 允许用户拖动头像, 默认 false。**手势拖动**: 在角色上按住左键拖 (触屏: 手指拖) = 移动整个头像 (识别在 iframe 里, 复用 Tauri 桌宠的手势状态机,
43
+ * 宿主执行移动并限幅在视口内); 透明 + 穿透场景下只有点在角色上才接管, 其余位置仍穿透给宿主页。内联 (position: 'inline') 用 CSS translate 位移, 不改变布局流。
44
+ * 悬浮模式 (bottom-right / bottom-left) 同样走手势 (改 left/top)。运行中可用 `setDraggable()` 开关, 不重建 iframe。
45
+ * 需要 /embed 的 capabilities.gestures (旧版没有 → 手势无效, 并触发一次 error{code:'unsupported'})。
46
+ */
34
47
  draggable?: boolean;
48
+ /**
49
+ * 外壳圆角 (iframe / 占位图 / 外壳一起裁), 数字 = px, 或任意 CSS 长度。默认: **非透明场景 (light / dark) = 20px** (与 Tauri 桌宠窗口的圆角同值,
50
+ * 见 XC_WINDOW_CORNER_RADIUS), 透明场景 = 0 (没有底色可裁, 角色不被裁角)。`--xc-radius` CSS 变量优先级更高。运行中可用 `setBorderRadius()` 改, 不重建 iframe。
51
+ * 圆角半径是固定 px, 缩放 (resizable) 时不随尺寸变形。传 0 恢复方角。
52
+ */
53
+ borderRadius?: number | string;
54
+ /**
55
+ * 允许用户缩放头像, 默认 false。**角落缩放**: iframe 四个角各有一个 40px 热区 (悬停出现圆弧提示, 光标变成缩放箭头), 按住拖动 = 缩放, 对角固定。
56
+ * true = 默认限幅 (最小 120x180, 最大只受视口限制); 对象可自定义 minWidth / minHeight / maxWidth / maxHeight (px)。
57
+ * 缩放只改外壳尺寸, **不重建 iframe**: 模型 / 动画 / 对话状态都保持。通过 `resize` 事件拿到新尺寸, `getBox()` 随时读取。运行中可用 `setResizable()` 开关。
58
+ * 需要 capabilities.gestures (旧版 /embed 无效, 触发一次 error{code:'unsupported'})。
59
+ */
60
+ resizable?: boolean | XiaochunResizeLimits;
35
61
  lang?: XcLang;
36
- /** 初始服装: 内置 key (如 'xiaochun_maid') 或 https .vrm/.vrmaddon URL。 */
62
+ /** 初始服装: 内置服装 id (如 'xiaochun_maid', 见 getOutfits())。未知 id 回退默认服装, 并触发 error{code:'unknown_id'}。 */
63
+ outfit?: string;
64
+ /**
65
+ * @deprecated 请改用 `outfit`。值是内置服装 id 时等价于 `outfit`; https URL 不能再通过创建选项传入
66
+ * (任意 URL 需要 `allowCustomModel: true`, 之后用 `setModel({ url })`)。
67
+ */
37
68
  model?: string;
38
- /** 显示 iframe 内置 ChatBar (默认 false)。 */
39
- ui?: boolean;
69
+ /**
70
+ * 允许 `setModel({ url })` 加载任意 https .vrm/.vrmaddon/.vrmbase, 默认 false (关闭)。
71
+ * 模型是第三方文件, 会在 iframe 里被解析, 只在你信任该 URL 时打开。内置服装 (`setOutfit`) 不受影响。
72
+ */
73
+ allowCustomModel?: boolean;
74
+ /**
75
+ * 偏好 (服装 + 场景) 在宿主页的额外保存 (可选), 默认 false。保存位置是**宿主页**的 localStorage; 读出来后作为显式值传给 iframe, 所以会盖过 iframe 自己 localStorage 里存的 (iframe 本来就会记住用户的选择, 但它的存储可能被浏览器分区)。
76
+ * false 不保存
77
+ * 'host' 保存到 localStorage['xiaochun:prefs']
78
+ * 其它字符串 保存到 localStorage[该字符串] (同页多个实例互不覆盖时用)
79
+ * 优先级: 显式的 outfit / scene 选项 > 已保存的偏好 > 默认。只有 setOutfit / setScene 引起的变化会写入。
80
+ */
81
+ persist?: false | 'host' | (string & {});
82
+ /**
83
+ * 要显示的 iframe 内置界面部件 (数组, 默认不写 = 全不显示):
84
+ * 'chat' 底部聊天栏 (对话走 WebLLM, 会多下载一部分代码)
85
+ * 'bubble' 头顶气泡 (说话文本 / 状态)
86
+ * 'outfit' 换装按钮 } 外观/文案/交互与主站 TopHeader 一致; 点按钮走和 setOutfit() / setScene() 同一条白名单 + 串行 (last-wins) 路径,
87
+ * 'scene' 换场景按钮 } 照常触发 outfit-changed / scene-changed, 宿主用 SDK 换装时按钮状态同步。偏好由 iframe 自己的 localStorage 记住 (显式 outfit/scene 优先); 想自己存也可用 persist 或监听事件。
88
+ * 例: `ui: ['outfit', 'scene']`。未知名字忽略并 console.warn。创建期选项: 变化会重建 iframe。
89
+ * 'outfit' / 'scene' 只对新版 /embed 有效 (旧版忽略未知部件名)。
90
+ * @deprecated 布尔写法: `ui: true` (0.1.14 的旧写法) 等价于 `['chat', 'bubble']` 并 console.warn; 不要再用。
91
+ */
92
+ ui?: XcUiPart[] | boolean;
40
93
  heavy?: XcHeavyMode;
41
- /** 放开 iframe 内滚轮缩放 (默认锁, 防止吞宿主滚动)。 */
94
+ /**
95
+ * 自动预取服装资源到 iframe 的 IndexedDB (之后 setOutfit 不走网络), 默认 false。
96
+ * true = 全部内置服装 **除了**婚纱 (13.9MB); string[] = 只预取这些 id。
97
+ * 只在 `heavy: 'eager'` 时自动触发 (模型首次加载完成后发一次 xc.prefetch): 并发 1, 排在 EMAGE 加载之后, 不会和换装抢带宽。
98
+ * heavy 为默认 'lazy' 时本选项不生效 —— 想按需预取请直接调用 `prefetch()`。
99
+ */
100
+ prefetch?: boolean | string[];
101
+ /**
102
+ * iframe 内滚轮缩放, 默认 true (与主站一致)。透明场景只在指针落在角色上时缩放, 其余位置滚轮穿透给宿主页;
103
+ * 不透明场景整个 iframe 区域的滚轮 = 缩放 (会吞掉该区域的页面滚动)。传 false 锁定 (URL controls=0)。
104
+ */
42
105
  controls?: boolean;
43
106
  /** 视口外自动 xc.pause / 回来 xc.resume, 默认 true。 */
44
107
  autoPause?: boolean;
@@ -48,6 +111,13 @@ export interface XiaochunOptions {
48
111
  sandbox?: string | false;
49
112
  /** 握手超时 (ms), 超时 emit error{code:'timeout'}。调大适合慢网络, 调小反馈更快。默认 20000。 */
50
113
  handshakeTimeout?: number;
114
+ /**
115
+ * 给 iframe 的 `allow` 追加 `cross-origin-isolated` (默认 false: allow 仍是 'microphone; autoplay')。
116
+ * 仅当宿主页自己已跨源隔离 (响应头 COOP: same-origin + COEP: credentialless / require-corp) 时才生效;
117
+ * 开启后 iframe 内 crossOriginIsolated=true → EMAGE 的 onnxruntime-web 可用多线程 wasm (SharedArrayBuffer)。
118
+ * 副作用: 宿主页上的第三方资源/iframe 也必须满足 COEP, 见 docs/EMBED.md。宿主未隔离时开了也无害 (浏览器忽略)。
119
+ */
120
+ crossOriginIsolated?: boolean;
51
121
  zIndex?: number;
52
122
  }
53
123
  /** speakAudio / speakAudioStream 的选项。 */
@@ -85,6 +155,14 @@ export interface XiaochunError {
85
155
  message: string;
86
156
  command?: string;
87
157
  }
158
+ /** `move` / `resize` 事件载荷: 手势各阶段外壳在视口中的位置与尺寸 (CSS px)。 */
159
+ export interface XiaochunBoxEvent {
160
+ phase: 'start' | 'move' | 'end';
161
+ left: number;
162
+ top: number;
163
+ width: number;
164
+ height: number;
165
+ }
88
166
  export interface XiaochunEvents {
89
167
  /** xc.ready 握手成功 (协议层)。 */
90
168
  handshake: XcReadyPayload;
@@ -97,6 +175,14 @@ export interface XiaochunEvents {
97
175
  stt: XcSttPayload;
98
176
  utterance: XcUtterancePayload;
99
177
  'hit-region': XcHitRegionPayload;
178
+ /** 服装已生效 (含首次加载, initial:true)。同目标的重复请求不会再触发。 */
179
+ 'outfit-changed': XcOutfitChangedPayload;
180
+ /** 场景已生效 (含握手后上报的当前场景, initial:true)。 */
181
+ 'scene-changed': XcSceneChangedPayload;
182
+ /** 用户拖动头像 (draggable): start / move / end 各发一次, 位置已限幅。 */
183
+ move: XiaochunBoxEvent;
184
+ /** 用户缩放头像 (resizable): start / move / end 各发一次, 尺寸已限幅。 */
185
+ resize: XiaochunBoxEvent;
100
186
  error: XiaochunError;
101
187
  destroy: undefined;
102
188
  }
@@ -123,12 +209,54 @@ export interface XiaochunInstance {
123
209
  expression(name: XcExpressionPayload['name']): Promise<void>;
124
210
  /** TODO: 协议已预留, /embed 暂未实现 → 会收到 error{code:'unsupported'}。 */
125
211
  lookAt(x: number, y: number): Promise<void>;
212
+ /**
213
+ * 换内置服装 (id 见 getOutfits())。Promise 在服装**生效后** resolve (xc.outfit-changed)。
214
+ * 并发规则: 串行 + last-wins —— 加载中再次调用会排队, 排队的旧请求被更新的顶掉时 reject `[busy]` (可忽略, 以最新一次为准);
215
+ * 说话不会被打断, 换装在后台完成后才生效。目标已是当前服装则直接 resolve。
216
+ * 旧版 /embed (握手里没有 capabilities.outfits) 会 reject `[unsupported]`。
217
+ */
218
+ setOutfit(id: string): Promise<void>;
219
+ /** 切场景 ('light' | 'dark' | 'transparent', 见 getScenes())。SDK 同步外壳背景、穿透开关与 pointer-events。 */
220
+ setScene(id: XcSceneId | (string & {})): Promise<void>;
221
+ /**
222
+ * 预取内置服装资源到 iframe 的 IndexedDB (只下载, 不解压不合成)。ids 省略 = 全部内置服装, 婚纱 (13.9MB) 除外; 显式点名则照做。
223
+ * 全局串行 (并发 1); resolve 的结果里 failed 非空时可稍后重试。进度见 progress 事件 (payload.phase === 'prefetch')。
224
+ * 旧版 /embed reject `[unsupported]`; 用户开了省流量模式时 resolve 且 skipped:'save-data'。
225
+ */
226
+ prefetch(ids?: string[]): Promise<XcPrefetchedPayload>;
227
+ /** 握手后返回可换的内置服装 (不含裸模); 旧版 /embed 返回 []。会触发 iframe 创建 (lazy 模式)。 */
228
+ getOutfits(): Promise<XcOutfitInfo[]>;
229
+ /** 握手后返回可切的场景; 旧版 /embed 返回 []。 */
230
+ getScenes(): Promise<XcSceneInfo[]>;
231
+ /** 最近一次 xc.outfit-changed 的服装 id (自定义 URL 模型 / 尚未加载 = null)。 */
232
+ readonly outfit: string | null;
233
+ /** 最近一次 xc.scene-changed 的场景 id (握手前 = null)。 */
234
+ readonly scene: string | null;
235
+ /**
236
+ * 旧命令, 保留兼容。内置服装请改用 `setOutfit` (并发更稳、能拿到完成通知)。
237
+ * `{ url }` 默认被拒绝 (reject `[unsupported]`), 需创建选项 `allowCustomModel: true`。
238
+ */
126
239
  setModel(m: string | {
127
240
  url?: string;
128
241
  outfit?: string;
129
242
  name?: string;
130
243
  }): Promise<void>;
131
244
  setConfig(cfg: XcConfig): Promise<void>;
245
+ /** 运行时设置外壳尺寸 (数字 = px, 字符串 = CSS 长度); 不重建 iframe。用户缩放后宿主要同步自己的状态时用 `resize` 事件。 */
246
+ setSize(width: number | string, height: number | string): void;
247
+ /** 外壳当前在视口中的位置与尺寸 (CSS px)。 */
248
+ getBox(): {
249
+ left: number;
250
+ top: number;
251
+ width: number;
252
+ height: number;
253
+ };
254
+ /** 运行时开关手势拖动 (等价于 `draggable` 选项), 不重建 iframe。 */
255
+ /** 改外壳圆角 (数字 px / CSS 长度); 传 undefined 恢复默认 (非透明 20px / 透明 0)。 */
256
+ setBorderRadius(radius: number | string | undefined): void;
257
+ setDraggable(on: boolean): void;
258
+ /** 运行时开关 / 调整角落缩放 (等价于 `resizable` 选项), 不重建 iframe。false = 关。 */
259
+ setResizable(on: boolean | XiaochunResizeLimits): void;
132
260
  startListening(): Promise<void>;
133
261
  stopListening(): Promise<void>;
134
262
  mic(enabled: boolean): Promise<void>;