@firetable/project-xiaochun 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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": ";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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;",
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 /** iframe \u5185 `self.crossOriginIsolated` (true = \u53EF\u7528 SharedArrayBuffer / \u591A\u7EBF\u7A0B wasm)\u3002\u65E7\u7248 /embed \u6CA1\u6709\u8FD9\u4E2A\u5B57\u6BB5\u3002\u4EC5\u8BCA\u65AD\u7528\u3002 */\n crossOriginIsolated?: boolean;\n };\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": ";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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;AA8OO,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
6
  "names": []
7
7
  }
@@ -186,6 +186,8 @@ export interface XcReadyPayload {
186
186
  streaming: boolean;
187
187
  maxSeconds: number;
188
188
  };
189
+ /** iframe 内 `self.crossOriginIsolated` (true = 可用 SharedArrayBuffer / 多线程 wasm)。旧版 /embed 没有这个字段。仅诊断用。 */
190
+ crossOriginIsolated?: boolean;
189
191
  };
190
192
  }
191
193
  export interface XcLoadProgressPayload {
package/dist/protocol.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! @firetable/project-xiaochun v0.1.13 | MIT | https://github.com/FireTable/project-xiaochun */
1
+ /*! @firetable/project-xiaochun v0.1.14 | MIT | https://github.com/FireTable/project-xiaochun */
2
2
  import {
3
3
  XC_DEFAULT_ORIGIN,
4
4
  XC_DEFAULT_SRC,
@@ -11,7 +11,7 @@ import {
11
11
  isXcEnvelope,
12
12
  normalizeOrigin,
13
13
  xcMessage
14
- } from "./chunks/chunk-UB2HFUBS.js";
14
+ } from "./chunks/chunk-DUU5PTKT.js";
15
15
  export {
16
16
  XC_DEFAULT_ORIGIN,
17
17
  XC_DEFAULT_SRC,
package/dist/react.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! @firetable/project-xiaochun v0.1.13 | MIT | https://github.com/FireTable/project-xiaochun */
1
+ /*! @firetable/project-xiaochun v0.1.14 | MIT | https://github.com/FireTable/project-xiaochun */
2
2
  "use strict";
3
3
  "use client";
4
4
  var __defProp = Object.defineProperty;
@@ -282,7 +282,7 @@ function createXiaochun(options) {
282
282
  const f = document.createElement("iframe");
283
283
  f.title = "Project XiaoChun";
284
284
  f.loading = "lazy";
285
- f.allow = "microphone; autoplay";
285
+ f.allow = options.crossOriginIsolated === true ? "microphone; autoplay; cross-origin-isolated" : "microphone; autoplay";
286
286
  f.referrerPolicy = "strict-origin-when-cross-origin";
287
287
  if (options.sandbox !== false) {
288
288
  f.setAttribute("sandbox", options.sandbox ?? "allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox");
@@ -397,8 +397,26 @@ function createXiaochun(options) {
397
397
  break;
398
398
  }
399
399
  }
400
+ const POINTER_MIN_INTERVAL_MS = 32;
401
+ const POINTER_MIN_MOVE_PX = 2;
400
402
  let pointerRaf = 0;
401
403
  let lastPtr = null;
404
+ let lastPosted = null;
405
+ let rectCache = null;
406
+ let rectRo = null;
407
+ let rectAt = 0;
408
+ const invalidateRect = () => {
409
+ rectCache = null;
410
+ };
411
+ const getRect = () => {
412
+ if (!iframe) return null;
413
+ const now = performance.now();
414
+ if (!rectCache || now - rectAt > 400) {
415
+ rectCache = iframe.getBoundingClientRect();
416
+ rectAt = now;
417
+ }
418
+ return rectCache;
419
+ };
402
420
  const onHostPointer = (e) => {
403
421
  if (!iframe || !port || iframe.style.pointerEvents === "auto") return;
404
422
  lastPtr = { x: e.clientX, y: e.clientY };
@@ -406,20 +424,33 @@ function createXiaochun(options) {
406
424
  pointerRaf = requestAnimationFrame(() => {
407
425
  pointerRaf = 0;
408
426
  if (!iframe || !port || !lastPtr) return;
409
- const r = iframe.getBoundingClientRect();
427
+ const r = getRect();
428
+ if (!r) return;
410
429
  const x = lastPtr.x - r.left, y = lastPtr.y - r.top;
411
430
  if (x < 0 || y < 0 || x > r.width || y > r.height) {
431
+ lastPosted = null;
412
432
  if (lastHit) {
413
433
  lastHit = false;
414
434
  iframe.style.pointerEvents = "none";
415
435
  }
416
436
  return;
417
437
  }
438
+ const now = performance.now();
439
+ if (lastPosted && now - lastPosted.t < POINTER_MIN_INTERVAL_MS) return;
440
+ if (lastPosted && Math.abs(x - lastPosted.x) < POINTER_MIN_MOVE_PX && Math.abs(y - lastPosted.y) < POINTER_MIN_MOVE_PX) return;
441
+ lastPosted = { x, y, t: now };
418
442
  port.postMessage(xcMessage("xc.pointer", { x, y }));
419
443
  });
420
444
  };
421
445
  function setupPassthrough() {
422
- if (passthrough) window.addEventListener("pointermove", onHostPointer, { passive: true });
446
+ if (!passthrough) return;
447
+ window.addEventListener("pointermove", onHostPointer, { passive: true });
448
+ window.addEventListener("scroll", invalidateRect, { passive: true, capture: true });
449
+ window.addEventListener("resize", invalidateRect, { passive: true });
450
+ if (typeof ResizeObserver !== "undefined" && iframe) {
451
+ rectRo = new ResizeObserver(invalidateRect);
452
+ rectRo.observe(iframe);
453
+ }
423
454
  }
424
455
  let pauseIo = null;
425
456
  function setupAutoPause() {
@@ -507,6 +538,7 @@ function createXiaochun(options) {
507
538
  ws.top = `${Math.min(Math.max(0, e.clientY - drag.dy), window.innerHeight - 40)}px`;
508
539
  ws.right = "auto";
509
540
  ws.bottom = "auto";
541
+ invalidateRect();
510
542
  });
511
543
  const end = () => {
512
544
  drag = null;
@@ -556,6 +588,9 @@ function createXiaochun(options) {
556
588
  if (pointerRaf) cancelAnimationFrame(pointerRaf);
557
589
  window.removeEventListener("message", onWindowMessage);
558
590
  window.removeEventListener("pointermove", onHostPointer);
591
+ window.removeEventListener("scroll", invalidateRect, true);
592
+ window.removeEventListener("resize", invalidateRect);
593
+ rectRo?.disconnect();
559
594
  try {
560
595
  port?.close();
561
596
  } catch {
@@ -651,6 +686,7 @@ function useXiaochun(options = {}) {
651
686
  o.passthrough,
652
687
  o.sandbox,
653
688
  o.handshakeTimeout,
689
+ o.crossOriginIsolated,
654
690
  o.zIndex
655
691
  ]);
656
692
  const first = (0, import_react.useRef)({ lang: o.lang, model: o.model });