@jingyi0605/codingns4dsh 0.2.1-beta.5 → 0.2.1-beta.6

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.
Files changed (301) hide show
  1. package/README.en.md +112 -161
  2. package/README.md +106 -263
  3. package/assets/provider-icons/doubao.png +0 -0
  4. package/data/build/dist/client/avatar/catalog-panel.d.ts +1 -1
  5. package/data/build/dist/client/avatar/catalog-panel.d.ts.map +1 -1
  6. package/data/build/dist/client/avatar/catalog-panel.js +13 -11
  7. package/data/build/dist/client/avatar/catalog-panel.js.map +1 -1
  8. package/data/build/dist/client/avatar/engine.d.ts.map +1 -1
  9. package/data/build/dist/client/avatar/engine.js +26 -7
  10. package/data/build/dist/client/avatar/engine.js.map +1 -1
  11. package/data/build/dist/client/avatar/manager.d.ts.map +1 -1
  12. package/data/build/dist/client/avatar/manager.js +5 -1
  13. package/data/build/dist/client/avatar/manager.js.map +1 -1
  14. package/data/build/dist/client/avatar/slot.d.ts.map +1 -1
  15. package/data/build/dist/client/avatar/slot.js +4 -1
  16. package/data/build/dist/client/avatar/slot.js.map +1 -1
  17. package/data/build/dist/client/bundle.js +49155 -99509
  18. package/data/build/dist/client/bundle.js.map +1 -1
  19. package/data/build/dist/client/cli-catalog.d.ts +2 -0
  20. package/data/build/dist/client/cli-catalog.d.ts.map +1 -1
  21. package/data/build/dist/client/cli-catalog.js +8 -0
  22. package/data/build/dist/client/cli-catalog.js.map +1 -1
  23. package/data/build/dist/client/cli-slots.js +2 -2
  24. package/data/build/dist/client/cli-slots.js.map +1 -1
  25. package/data/build/dist/client/client.assistant-configuration-view.js +4526 -0
  26. package/data/build/dist/client/client.assistant-configuration-view.js.map +1 -0
  27. package/data/build/dist/client/client.assistant-debug-workbench.js +764 -0
  28. package/data/build/dist/client/client.assistant-debug-workbench.js.map +1 -0
  29. package/data/build/dist/client/client.assistant-prompt-editor.js +76 -0
  30. package/data/build/dist/client/client.assistant-prompt-editor.js.map +1 -0
  31. package/data/build/dist/client/client.assistant-prompts.js +44 -0
  32. package/data/build/dist/client/client.assistant-prompts.js.map +1 -0
  33. package/data/build/dist/client/client.assistant-voice-sessions.js +144 -0
  34. package/data/build/dist/client/client.assistant-voice-sessions.js.map +1 -0
  35. package/data/build/dist/client/client.assistant-workbench.js +2929 -0
  36. package/data/build/dist/client/client.assistant-workbench.js.map +1 -0
  37. package/data/build/dist/client/client.delegate-command.js +1315 -0
  38. package/data/build/dist/client/client.delegate-command.js.map +1 -0
  39. package/data/build/dist/client/client.editor-engine.js +32486 -0
  40. package/data/build/dist/client/client.editor-engine.js.map +1 -0
  41. package/data/build/dist/client/client.manager.js +3454 -0
  42. package/data/build/dist/client/client.manager.js.map +1 -0
  43. package/data/build/dist/client/client.model-catalog-cache.js +543 -0
  44. package/data/build/dist/client/client.model-catalog-cache.js.map +1 -0
  45. package/data/build/dist/client/client.native-workspace-store.js +689 -0
  46. package/data/build/dist/client/client.native-workspace-store.js.map +1 -0
  47. package/data/build/dist/client/client.primitives-adapter.js +333 -0
  48. package/data/build/dist/client/client.primitives-adapter.js.map +1 -0
  49. package/data/build/dist/client/client.react-dom.js +7286 -0
  50. package/data/build/dist/client/client.react-dom.js.map +1 -0
  51. package/data/build/dist/client/client.rolldown-runtime.js +59 -0
  52. package/data/build/dist/client/client.theme.js +386 -0
  53. package/data/build/dist/client/client.theme.js.map +1 -0
  54. package/data/build/dist/client/client.transport.js +3323 -0
  55. package/data/build/dist/client/client.transport.js.map +1 -0
  56. package/data/build/dist/client/client.xterm-engine.js +13064 -0
  57. package/data/build/dist/client/client.xterm-engine.js.map +1 -0
  58. package/data/build/dist/client/editor-engine.d.ts +4 -0
  59. package/data/build/dist/client/editor-engine.d.ts.map +1 -0
  60. package/data/build/dist/client/editor-engine.js +50 -0
  61. package/data/build/dist/client/editor-engine.js.map +1 -0
  62. package/data/build/dist/client/engine-loader.d.ts +5 -0
  63. package/data/build/dist/client/engine-loader.d.ts.map +1 -0
  64. package/data/build/dist/client/engine-loader.js +35 -0
  65. package/data/build/dist/client/engine-loader.js.map +1 -0
  66. package/data/build/dist/client/features/assistant-configuration-loader.d.ts +14 -0
  67. package/data/build/dist/client/features/assistant-configuration-loader.d.ts.map +1 -0
  68. package/data/build/dist/client/features/assistant-configuration-loader.js +13 -0
  69. package/data/build/dist/client/features/assistant-configuration-loader.js.map +1 -0
  70. package/data/build/dist/client/features/assistant-configuration-session.d.ts.map +1 -1
  71. package/data/build/dist/client/features/assistant-configuration-session.js +13 -4
  72. package/data/build/dist/client/features/assistant-configuration-session.js.map +1 -1
  73. package/data/build/dist/client/features/assistant-configuration-view.d.ts +66 -0
  74. package/data/build/dist/client/features/assistant-configuration-view.d.ts.map +1 -0
  75. package/data/build/dist/client/features/assistant-configuration-view.js +133 -0
  76. package/data/build/dist/client/features/assistant-configuration-view.js.map +1 -0
  77. package/data/build/dist/client/features/assistant-debug-workbench.d.ts +4 -11
  78. package/data/build/dist/client/features/assistant-debug-workbench.d.ts.map +1 -1
  79. package/data/build/dist/client/features/assistant-debug-workbench.js +41 -68
  80. package/data/build/dist/client/features/assistant-debug-workbench.js.map +1 -1
  81. package/data/build/dist/client/features/assistant-display-store.d.ts +57 -0
  82. package/data/build/dist/client/features/assistant-display-store.d.ts.map +1 -0
  83. package/data/build/dist/client/features/assistant-display-store.js +189 -0
  84. package/data/build/dist/client/features/assistant-display-store.js.map +1 -0
  85. package/data/build/dist/client/features/assistant-prompt-editor.d.ts +12 -0
  86. package/data/build/dist/client/features/assistant-prompt-editor.d.ts.map +1 -0
  87. package/data/build/dist/client/features/assistant-prompt-editor.js +14 -0
  88. package/data/build/dist/client/features/assistant-prompt-editor.js.map +1 -0
  89. package/data/build/dist/client/features/assistant-view-loader.d.ts +17 -0
  90. package/data/build/dist/client/features/assistant-view-loader.d.ts.map +1 -0
  91. package/data/build/dist/client/features/assistant-view-loader.js +49 -0
  92. package/data/build/dist/client/features/assistant-view-loader.js.map +1 -0
  93. package/data/build/dist/client/features/assistant-workbench.d.ts +1 -60
  94. package/data/build/dist/client/features/assistant-workbench.d.ts.map +1 -1
  95. package/data/build/dist/client/features/assistant-workbench.js +41 -169
  96. package/data/build/dist/client/features/assistant-workbench.js.map +1 -1
  97. package/data/build/dist/client/features/cli-adapters.js +3 -7
  98. package/data/build/dist/client/features/cli-adapters.js.map +1 -1
  99. package/data/build/dist/client/features/global-voice-assistant.d.ts.map +1 -1
  100. package/data/build/dist/client/features/global-voice-assistant.js +21 -20
  101. package/data/build/dist/client/features/global-voice-assistant.js.map +1 -1
  102. package/data/build/dist/client/features/peer-host.d.ts.map +1 -1
  103. package/data/build/dist/client/features/peer-host.js +14 -25
  104. package/data/build/dist/client/features/peer-host.js.map +1 -1
  105. package/data/build/dist/client/file-management-dom.d.ts +3 -1
  106. package/data/build/dist/client/file-management-dom.d.ts.map +1 -1
  107. package/data/build/dist/client/file-management-dom.js +85 -79
  108. package/data/build/dist/client/file-management-dom.js.map +1 -1
  109. package/data/build/dist/client/locales/assistantAvatar.d.ts +1 -1
  110. package/data/build/dist/client/locales/assistantAvatar.js +2 -2
  111. package/data/build/dist/client/locales/assistantAvatar.js.map +1 -1
  112. package/data/build/dist/client/locales/fileWorkspace.d.ts.map +1 -1
  113. package/data/build/dist/client/locales/fileWorkspace.js +2 -0
  114. package/data/build/dist/client/locales/fileWorkspace.js.map +1 -1
  115. package/data/build/dist/client/peer-host-management-api.d.ts +2 -2
  116. package/data/build/dist/client/peer-host-management-api.d.ts.map +1 -1
  117. package/data/build/dist/client/peer-host-management-api.js +8 -5
  118. package/data/build/dist/client/peer-host-management-api.js.map +1 -1
  119. package/data/build/dist/client/provider-icon-assets.js +5 -39
  120. package/data/build/dist/client/provider-icon-assets.js.map +1 -1
  121. package/data/build/dist/client/provider-icon-bridge.d.ts +6 -0
  122. package/data/build/dist/client/provider-icon-bridge.d.ts.map +1 -0
  123. package/data/build/dist/client/provider-icon-bridge.js +65 -0
  124. package/data/build/dist/client/provider-icon-bridge.js.map +1 -0
  125. package/data/build/dist/client/provider-icons.d.ts +1 -0
  126. package/data/build/dist/client/provider-icons.d.ts.map +1 -1
  127. package/data/build/dist/client/provider-icons.js +2 -1
  128. package/data/build/dist/client/provider-icons.js.map +1 -1
  129. package/data/build/dist/client/remote-web-context.d.ts +2 -0
  130. package/data/build/dist/client/remote-web-context.d.ts.map +1 -1
  131. package/data/build/dist/client/remote-web-context.js +50 -2
  132. package/data/build/dist/client/remote-web-context.js.map +1 -1
  133. package/data/build/dist/client/serial-polling.d.ts +3 -1
  134. package/data/build/dist/client/serial-polling.d.ts.map +1 -1
  135. package/data/build/dist/client/serial-polling.js +10 -3
  136. package/data/build/dist/client/serial-polling.js.map +1 -1
  137. package/data/build/dist/client/settings-section.js +8 -2
  138. package/data/build/dist/client/settings-section.js.map +1 -1
  139. package/data/build/dist/client/terminal/xterm-engine.d.ts +4 -0
  140. package/data/build/dist/client/terminal/xterm-engine.d.ts.map +1 -0
  141. package/data/build/dist/client/terminal/xterm-engine.js +5 -0
  142. package/data/build/dist/client/terminal/xterm-engine.js.map +1 -0
  143. package/data/build/dist/client/terminal/xterm-view.d.ts.map +1 -1
  144. package/data/build/dist/client/terminal/xterm-view.js +33 -25
  145. package/data/build/dist/client/terminal/xterm-view.js.map +1 -1
  146. package/data/build/dist/host/avatar/temporary-previews.d.ts +3 -1
  147. package/data/build/dist/host/avatar/temporary-previews.d.ts.map +1 -1
  148. package/data/build/dist/host/avatar/temporary-previews.js +7 -2
  149. package/data/build/dist/host/avatar/temporary-previews.js.map +1 -1
  150. package/data/build/dist/host/cli-adapters/antigravity-driver.d.ts.map +1 -1
  151. package/data/build/dist/host/cli-adapters/antigravity-driver.js +2 -2
  152. package/data/build/dist/host/cli-adapters/antigravity-driver.js.map +1 -1
  153. package/data/build/dist/host/cli-adapters/binary-detection.d.ts +19 -0
  154. package/data/build/dist/host/cli-adapters/binary-detection.d.ts.map +1 -0
  155. package/data/build/dist/host/cli-adapters/binary-detection.js +71 -0
  156. package/data/build/dist/host/cli-adapters/binary-detection.js.map +1 -0
  157. package/data/build/dist/host/cli-adapters/claude-driver.d.ts +2 -0
  158. package/data/build/dist/host/cli-adapters/claude-driver.d.ts.map +1 -1
  159. package/data/build/dist/host/cli-adapters/claude-driver.js +4 -0
  160. package/data/build/dist/host/cli-adapters/claude-driver.js.map +1 -1
  161. package/data/build/dist/host/cli-adapters/claude-model-options.d.ts.map +1 -1
  162. package/data/build/dist/host/cli-adapters/claude-model-options.js +2 -1
  163. package/data/build/dist/host/cli-adapters/claude-model-options.js.map +1 -1
  164. package/data/build/dist/host/cli-adapters/claude-process.d.ts +7 -0
  165. package/data/build/dist/host/cli-adapters/claude-process.d.ts.map +1 -0
  166. package/data/build/dist/host/cli-adapters/claude-process.js +59 -0
  167. package/data/build/dist/host/cli-adapters/claude-process.js.map +1 -0
  168. package/data/build/dist/host/cli-adapters/codebuddy-driver.d.ts +3 -0
  169. package/data/build/dist/host/cli-adapters/codebuddy-driver.d.ts.map +1 -1
  170. package/data/build/dist/host/cli-adapters/codebuddy-driver.js +18 -5
  171. package/data/build/dist/host/cli-adapters/codebuddy-driver.js.map +1 -1
  172. package/data/build/dist/host/cli-adapters/command-code-driver.d.ts +0 -1
  173. package/data/build/dist/host/cli-adapters/command-code-driver.d.ts.map +1 -1
  174. package/data/build/dist/host/cli-adapters/command-code-driver.js +6 -29
  175. package/data/build/dist/host/cli-adapters/command-code-driver.js.map +1 -1
  176. package/data/build/dist/host/cli-adapters/doubao-app.d.ts +27 -0
  177. package/data/build/dist/host/cli-adapters/doubao-app.d.ts.map +1 -0
  178. package/data/build/dist/host/cli-adapters/doubao-app.js +110 -0
  179. package/data/build/dist/host/cli-adapters/doubao-app.js.map +1 -0
  180. package/data/build/dist/host/cli-adapters/doubao-artifact-store.d.ts +16 -0
  181. package/data/build/dist/host/cli-adapters/doubao-artifact-store.d.ts.map +1 -0
  182. package/data/build/dist/host/cli-adapters/doubao-artifact-store.js +142 -0
  183. package/data/build/dist/host/cli-adapters/doubao-artifact-store.js.map +1 -0
  184. package/data/build/dist/host/cli-adapters/doubao-artifacts.d.ts +16 -0
  185. package/data/build/dist/host/cli-adapters/doubao-artifacts.d.ts.map +1 -0
  186. package/data/build/dist/host/cli-adapters/doubao-artifacts.js +34 -0
  187. package/data/build/dist/host/cli-adapters/doubao-artifacts.js.map +1 -0
  188. package/data/build/dist/host/cli-adapters/doubao-cdp.d.ts +49 -0
  189. package/data/build/dist/host/cli-adapters/doubao-cdp.d.ts.map +1 -0
  190. package/data/build/dist/host/cli-adapters/doubao-cdp.js +177 -0
  191. package/data/build/dist/host/cli-adapters/doubao-cdp.js.map +1 -0
  192. package/data/build/dist/host/cli-adapters/doubao-driver.d.ts +33 -0
  193. package/data/build/dist/host/cli-adapters/doubao-driver.d.ts.map +1 -0
  194. package/data/build/dist/host/cli-adapters/doubao-driver.js +291 -0
  195. package/data/build/dist/host/cli-adapters/doubao-driver.js.map +1 -0
  196. package/data/build/dist/host/cli-adapters/doubao-events.d.ts +21 -0
  197. package/data/build/dist/host/cli-adapters/doubao-events.d.ts.map +1 -0
  198. package/data/build/dist/host/cli-adapters/doubao-events.js +126 -0
  199. package/data/build/dist/host/cli-adapters/doubao-events.js.map +1 -0
  200. package/data/build/dist/host/cli-adapters/doubao-runtime.d.ts +6 -0
  201. package/data/build/dist/host/cli-adapters/doubao-runtime.d.ts.map +1 -0
  202. package/data/build/dist/host/cli-adapters/doubao-runtime.js +176 -0
  203. package/data/build/dist/host/cli-adapters/doubao-runtime.js.map +1 -0
  204. package/data/build/dist/host/cli-adapters/doubao-tools.d.ts +8 -0
  205. package/data/build/dist/host/cli-adapters/doubao-tools.d.ts.map +1 -0
  206. package/data/build/dist/host/cli-adapters/doubao-tools.js +139 -0
  207. package/data/build/dist/host/cli-adapters/doubao-tools.js.map +1 -0
  208. package/data/build/dist/host/cli-adapters/driver.d.ts +3 -2
  209. package/data/build/dist/host/cli-adapters/driver.d.ts.map +1 -1
  210. package/data/build/dist/host/cli-adapters/feature.d.ts.map +1 -1
  211. package/data/build/dist/host/cli-adapters/feature.js +2 -0
  212. package/data/build/dist/host/cli-adapters/feature.js.map +1 -1
  213. package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts.map +1 -1
  214. package/data/build/dist/host/cli-adapters/json-rpc-process.js +3 -1
  215. package/data/build/dist/host/cli-adapters/json-rpc-process.js.map +1 -1
  216. package/data/build/dist/host/cli-adapters/legacy-session-adapter.d.ts.map +1 -1
  217. package/data/build/dist/host/cli-adapters/legacy-session-adapter.js +1 -0
  218. package/data/build/dist/host/cli-adapters/legacy-session-adapter.js.map +1 -1
  219. package/data/build/dist/host/cli-adapters/model-catalog.d.ts +2 -0
  220. package/data/build/dist/host/cli-adapters/model-catalog.d.ts.map +1 -1
  221. package/data/build/dist/host/cli-adapters/model-catalog.js +10 -0
  222. package/data/build/dist/host/cli-adapters/model-catalog.js.map +1 -1
  223. package/data/build/dist/host/cli-adapters/opencode-driver.d.ts +1 -0
  224. package/data/build/dist/host/cli-adapters/opencode-driver.d.ts.map +1 -1
  225. package/data/build/dist/host/cli-adapters/opencode-driver.js +9 -28
  226. package/data/build/dist/host/cli-adapters/opencode-driver.js.map +1 -1
  227. package/data/build/dist/host/cli-adapters/process-utils.d.ts +4 -2
  228. package/data/build/dist/host/cli-adapters/process-utils.d.ts.map +1 -1
  229. package/data/build/dist/host/cli-adapters/process-utils.js +64 -11
  230. package/data/build/dist/host/cli-adapters/process-utils.js.map +1 -1
  231. package/data/build/dist/host/cli-adapters/qoder-driver.d.ts.map +1 -1
  232. package/data/build/dist/host/cli-adapters/qoder-driver.js +13 -33
  233. package/data/build/dist/host/cli-adapters/qoder-driver.js.map +1 -1
  234. package/data/build/dist/host/cli-adapters/registry.d.ts.map +1 -1
  235. package/data/build/dist/host/cli-adapters/registry.js +31 -4
  236. package/data/build/dist/host/cli-adapters/registry.js.map +1 -1
  237. package/data/build/dist/host/cli-adapters/rpc-driver-utils.d.ts +2 -11
  238. package/data/build/dist/host/cli-adapters/rpc-driver-utils.d.ts.map +1 -1
  239. package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +1 -28
  240. package/data/build/dist/host/cli-adapters/rpc-driver-utils.js.map +1 -1
  241. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +3 -3
  242. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts.map +1 -1
  243. package/data/build/dist/host/cli-adapters/standard-stream-driver.js +12 -37
  244. package/data/build/dist/host/cli-adapters/standard-stream-driver.js.map +1 -1
  245. package/data/build/dist/host/cli-adapters/windows-command-environment.d.ts +12 -0
  246. package/data/build/dist/host/cli-adapters/windows-command-environment.d.ts.map +1 -0
  247. package/data/build/dist/host/cli-adapters/windows-command-environment.js +53 -0
  248. package/data/build/dist/host/cli-adapters/windows-command-environment.js.map +1 -0
  249. package/data/build/dist/host/features/assistant-avatar-runtime.d.ts.map +1 -1
  250. package/data/build/dist/host/features/assistant-avatar-runtime.js +23 -14
  251. package/data/build/dist/host/features/assistant-avatar-runtime.js.map +1 -1
  252. package/data/build/dist/host/features/assistant-background.d.ts +44 -0
  253. package/data/build/dist/host/features/assistant-background.d.ts.map +1 -0
  254. package/data/build/dist/host/features/assistant-background.js +116 -0
  255. package/data/build/dist/host/features/assistant-background.js.map +1 -0
  256. package/data/build/dist/host/features/assistant-index-updates.d.ts +5 -0
  257. package/data/build/dist/host/features/assistant-index-updates.d.ts.map +1 -1
  258. package/data/build/dist/host/features/assistant-index-updates.js +29 -6
  259. package/data/build/dist/host/features/assistant-index-updates.js.map +1 -1
  260. package/data/build/dist/host/features/assistant-source-cache.d.ts +2 -2
  261. package/data/build/dist/host/features/assistant-source-cache.d.ts.map +1 -1
  262. package/data/build/dist/host/features/assistant-source-cache.js +4 -2
  263. package/data/build/dist/host/features/assistant-source-cache.js.map +1 -1
  264. package/data/build/dist/host/features/global-voice-rpc.d.ts.map +1 -1
  265. package/data/build/dist/host/features/global-voice-rpc.js +269 -110
  266. package/data/build/dist/host/features/global-voice-rpc.js.map +1 -1
  267. package/data/build/dist/host/features/peer-host.d.ts.map +1 -1
  268. package/data/build/dist/host/features/peer-host.js +61 -21
  269. package/data/build/dist/host/features/peer-host.js.map +1 -1
  270. package/data/build/dist/host/features/types.d.ts +6 -4
  271. package/data/build/dist/host/features/types.d.ts.map +1 -1
  272. package/data/build/dist/host/index.d.ts.map +1 -1
  273. package/data/build/dist/host/index.js +29 -19
  274. package/data/build/dist/host/index.js.map +1 -1
  275. package/data/build/dist/host/provider-icon-assets.d.ts +5 -0
  276. package/data/build/dist/host/provider-icon-assets.d.ts.map +1 -0
  277. package/data/build/dist/host/provider-icon-assets.js +61 -0
  278. package/data/build/dist/host/provider-icon-assets.js.map +1 -0
  279. package/data/build/dist/host/rpc.js +1 -1
  280. package/data/build/dist/host/rpc.js.map +1 -1
  281. package/data/build/dist/host/session-migration-repair.d.ts +4 -0
  282. package/data/build/dist/host/session-migration-repair.d.ts.map +1 -1
  283. package/data/build/dist/host/session-migration-repair.js +75 -2
  284. package/data/build/dist/host/session-migration-repair.js.map +1 -1
  285. package/data/build/dist/shared/assistant-avatar-engine.d.ts +2 -0
  286. package/data/build/dist/shared/assistant-avatar-engine.d.ts.map +1 -1
  287. package/data/build/dist/shared/assistant-avatar-engine.js +11 -1
  288. package/data/build/dist/shared/assistant-avatar-engine.js.map +1 -1
  289. package/data/build/dist/shared/contracts/cli-adapter.d.ts +3 -0
  290. package/data/build/dist/shared/contracts/cli-adapter.d.ts.map +1 -1
  291. package/data/build/dist/shared/contracts/version.d.ts +1 -1
  292. package/data/build/dist/shared/contracts/version.js +1 -1
  293. package/data/build/dist/shared/provider-icon-resources.d.ts +4 -0
  294. package/data/build/dist/shared/provider-icon-resources.d.ts.map +1 -0
  295. package/data/build/dist/shared/provider-icon-resources.js +24 -0
  296. package/data/build/dist/shared/provider-icon-resources.js.map +1 -0
  297. package/package.json +2 -2
  298. package/profile/README.md +2 -2
  299. package/profile/package.json +2 -2
  300. package/profile/version.json +1 -1
  301. package/version.json +1 -1
@@ -0,0 +1,3454 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@jingyi0605/codingns4dsh",
3
+ chunk: "client.manager.js",
4
+ factory: (require) => {
5
+ var module = { exports: {} };
6
+ var exports = module.exports;
7
+ const require_transport = require("@jingyi0605/codingns4dsh/client.transport.js");
8
+ const require_theme = require("@jingyi0605/codingns4dsh/client.theme.js");
9
+ require("@jingyi0605/codingns4dsh/client.primitives-adapter.js");
10
+ let react = require("react");
11
+ //#region src/dsh-capabilities/settings-store.ts
12
+ /** 将 DSH 两代写入结果统一成内部成功布尔值。 */
13
+ function accepted(write) {
14
+ return write.then((result) => result !== false);
15
+ }
16
+ /**
17
+ * 判断两份设置快照在内容上是否等价。
18
+ *
19
+ * DSH 的 ConfigForm、SettingsScope 和插件自己的 Host RPC 都会在每次读取时
20
+ * 构造新对象。`useSyncExternalStore` 要求 `getSnapshot()` 在内容未变化时返回
21
+ * 同一个引用,否则 React 会在每次渲染后重新比对快照并强制再次渲染,直到抛出
22
+ * “Maximum update depth exceeded”(React #185)。所有适配器都必须先比较内容、
23
+ * 再决定是否替换快照并唤醒订阅者。
24
+ */
25
+ function sameSettingsSnapshot(left, right) {
26
+ return sameSettingsValue(left.value, right.value) && left.revision === right.revision && left.writable === right.writable && left.status === right.status;
27
+ }
28
+ /** 设置内容的结构比较:数组按顺序、对象按键,标量按 Object.is。 */
29
+ function sameSettingsValue(left, right) {
30
+ if (Object.is(left, right)) return true;
31
+ if (left === null || right === null || typeof left !== "object" || typeof right !== "object") return false;
32
+ if (Array.isArray(left) || Array.isArray(right)) {
33
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
34
+ return left.every((value, index) => sameSettingsValue(value, right[index]));
35
+ }
36
+ const leftRecord = left;
37
+ const rightRecord = right;
38
+ const leftKeys = Object.keys(leftRecord);
39
+ const rightKeys = Object.keys(rightRecord);
40
+ if (leftKeys.length !== rightKeys.length) return false;
41
+ return leftKeys.every((key) => Object.prototype.hasOwnProperty.call(rightRecord, key) && sameSettingsValue(leftRecord[key], rightRecord[key]));
42
+ }
43
+ //#endregion
44
+ //#region src/shared/assistant-lifecycle.ts
45
+ const DEFAULT_ASSISTANT_NAME = "我的助理";
46
+ const ASSISTANT_PERSONALITY_MAX_CHARS = 4e3;
47
+ /** 只为未创建且尚无设定的助理补默认值;显式空白和已有档案保持原样。 */
48
+ function readAssistantPersonality(profile) {
49
+ return profile.personality ?? (profile.initialized ? "" : "你是一位友善、耐心、可靠的智能助理,是用户工作与生活中的伙伴。善于倾听,表达自然简洁,优先给出清晰结论和可执行建议,再按需解释细节。遇到不确定的信息会如实说明并主动澄清,尊重用户的选择与隐私,不编造事实或经历。");
50
+ }
51
+ /** 只有显式档案表示已创建;旧能力配置仅预填表单,不能跳过首次创建。 */
52
+ function readAssistantProfile(settings) {
53
+ if (settings.profile !== void 0) return { ...settings.profile };
54
+ return {
55
+ name: DEFAULT_ASSISTANT_NAME,
56
+ initialized: false,
57
+ createdAt: null
58
+ };
59
+ }
60
+ //#endregion
61
+ //#region src/shared/assistant-avatar-resources.ts
62
+ function assistantAvatarCacheStatusPath(pack) {
63
+ return `${pack.basePath}cache-status.json`;
64
+ }
65
+ Object.freeze([]);
66
+ //#endregion
67
+ //#region src/shared/assistant-avatar-legacy-resources.ts
68
+ /** 地址包含上游提交与精简策略版本;修改资源或策略时必须更新版本,避免浏览器旧缓存。 */
69
+ const WHALE_LIVE2D_RESOURCE_PACK = Object.freeze({
70
+ basePath: "/api/codingns/assistant-avatar-assets/whale-185c02bf-v1/",
71
+ upstreamRoot: "https://raw.githubusercontent.com/A8Chann/dsh-pet-live2d/185c02bfbb3b886e1a236b5c2ec035cc085b9ca2/dsh-live2d-pet/pets/ds-whale-girl/",
72
+ manifest: "c_0120.model3.json",
73
+ motionGroups: Object.freeze({
74
+ idle: "Idle",
75
+ thinking: "Idle",
76
+ listening: "Idle",
77
+ waiting: "Idle",
78
+ error: "SprayWater"
79
+ }),
80
+ files: Object.freeze([
81
+ {
82
+ path: "c_0120.model3.json",
83
+ contentType: "application/json",
84
+ maxBytes: 262144
85
+ },
86
+ {
87
+ path: "model/c_0120.moc3",
88
+ contentType: "application/octet-stream",
89
+ maxBytes: 4194304
90
+ },
91
+ {
92
+ path: "model/c_0120.physics3.json",
93
+ contentType: "application/json",
94
+ maxBytes: 1048576
95
+ },
96
+ {
97
+ path: "model/c_0120.cdi3.json",
98
+ contentType: "application/json",
99
+ maxBytes: 1048576,
100
+ preload: false
101
+ },
102
+ {
103
+ path: "textures/texture_00.png",
104
+ contentType: "image/png",
105
+ maxBytes: 4194304
106
+ },
107
+ {
108
+ path: "textures/texture_01.png",
109
+ contentType: "image/png",
110
+ maxBytes: 1048576
111
+ },
112
+ {
113
+ path: "motions/idle.motion3.json",
114
+ contentType: "application/json",
115
+ maxBytes: 1048576
116
+ },
117
+ {
118
+ path: "motions/spray-water.motion3.json",
119
+ contentType: "application/json",
120
+ maxBytes: 1048576
121
+ }
122
+ ].map((file) => Object.freeze(file)))
123
+ });
124
+ const ASSISTANT_AVATAR_RESOURCE_PACKS = Object.freeze([WHALE_LIVE2D_RESOURCE_PACK]);
125
+ //#endregion
126
+ //#region src/shared/assistant-avatar-legacy.ts
127
+ const spriteRoot = "https://raw.githubusercontent.com/YunYueSama/codex-deepseek-pet/7661c8b304c5400701f91da01b1a643a207331de/codex-deepseek-pet/";
128
+ const live2dRoot = WHALE_LIVE2D_RESOURCE_PACK.upstreamRoot;
129
+ /**
130
+ * 目录保存经 Codex/DSH 适配器转换并核验的统一模型数据,不包含美术文件。
131
+ * 固定提交避免上游 main 更新悄悄改变素材;读取目录不请求网络,选中后才由渲染器加载。
132
+ * 预设使用独立 ID,保留此前按上游 ID 导入的用户形象与自定义清单容量。
133
+ */
134
+ const LEGACY_ASSISTANT_AVATAR_PRESETS = Object.freeze([Object.freeze({
135
+ labelKey: "avatar.preset.whaleSprite",
136
+ model: Object.freeze({
137
+ id: "codingns-preset-whale-sprite",
138
+ name: "大肥鱼 · Q版全身",
139
+ renderer: "spritesheet",
140
+ source: `${spriteRoot}spritesheet.webp`,
141
+ spriteVersion: 2,
142
+ package: Object.freeze({
143
+ adapterId: "codex-pet",
144
+ manifestUrl: `${spriteRoot}pet.json`,
145
+ author: "YunYueSama",
146
+ license: "大肥鱼项目署名许可 1.0:仅授权作者有权授权的新增贡献;第三方角色与参考作品权利另行确认,非 DeepSeek 官方授权。",
147
+ homepage: "https://github.com/YunYueSama/codex-deepseek-pet/blob/7661c8b304c5400701f91da01b1a643a207331de/ASSET_LICENSE.md"
148
+ })
149
+ })
150
+ }), Object.freeze({
151
+ labelKey: "avatar.preset.whaleLive2d",
152
+ model: Object.freeze({
153
+ id: "codingns-preset-whale-live2d",
154
+ name: "大肥鱼 · Live2D 桌前",
155
+ renderer: "live2d",
156
+ source: `${WHALE_LIVE2D_RESOURCE_PACK.basePath}${WHALE_LIVE2D_RESOURCE_PACK.manifest}`,
157
+ spriteVersion: 2,
158
+ motionGroups: WHALE_LIVE2D_RESOURCE_PACK.motionGroups,
159
+ live2d: Object.freeze({
160
+ scale: 1,
161
+ position: Object.freeze([0, 0])
162
+ }),
163
+ package: Object.freeze({
164
+ adapterId: "dsh-live2d-pet",
165
+ manifestUrl: `${live2dRoot}pet.json`,
166
+ author: "模型:上善无形 / ZipZipPipe / 氵六青 · 插件:A8Chann",
167
+ license: "CC BY-NC-SA 4.0:署名、非商业、相同方式共享;商业使用须分别取得各权利人授权。清单已精简,模型与贴图未修改。",
168
+ homepage: "https://github.com/A8Chann/dsh-pet-live2d/blob/185c02bfbb3b886e1a236b5c2ec035cc085b9ca2/NOTICE.md"
169
+ })
170
+ })
171
+ })]);
172
+ function getAssistantAvatarPreset(id) {
173
+ return LEGACY_ASSISTANT_AVATAR_PRESETS.find((preset) => preset.model.id === id);
174
+ }
175
+ Object.freeze([]);
176
+ //#endregion
177
+ //#region src/shared/assistant-avatar-catalog.ts
178
+ /** 使用说明单独版本化;升级说明后必须重新确认,不能继承旧版同意。 */
179
+ const ASSISTANT_AVATAR_CONSENT_VERSION = "2026-10-07.1";
180
+ function validAssistantAvatarConsent(value) {
181
+ if (value === null || typeof value !== "object") return false;
182
+ const consent = value;
183
+ return typeof consent.version === "string" && consent.version.length > 0 && consent.version.length <= 80 && Number.isSafeInteger(consent.acceptedAt) && consent.acceptedAt > 0;
184
+ }
185
+ function hasAssistantAvatarConsent(value) {
186
+ return validAssistantAvatarConsent(value) && value.version === "2026-10-07.1";
187
+ }
188
+ //#endregion
189
+ //#region src/shared/assistant-avatar-engine.ts
190
+ let runtimeRevision = 0;
191
+ const runtimeListeners = /* @__PURE__ */ new Set();
192
+ /** Host 与 Client 各自维护代次,安装成功立即解除缺失引擎的短期缓存。 */
193
+ function invalidateAssistantAvatarRuntime() {
194
+ runtimeRevision += 1;
195
+ for (const listener of runtimeListeners) listener();
196
+ }
197
+ function assistantAvatarRuntimeRevision() {
198
+ return runtimeRevision;
199
+ }
200
+ /** 已经显示加载失败的形象也需要重新挂载,不能只清除模块缓存。 */
201
+ function subscribeAssistantAvatarRuntime(listener) {
202
+ runtimeListeners.add(listener);
203
+ return () => {
204
+ runtimeListeners.delete(listener);
205
+ };
206
+ }
207
+ /** 引擎许可单独版本化:Cubism SDK 条款与第三方素材许可分别确认。 */
208
+ const ASSISTANT_AVATAR_ENGINE_CONSENT_VERSION = "2026-10-08.1";
209
+ function validAssistantAvatarEngineConsent(value) {
210
+ if (value === null || typeof value !== "object") return false;
211
+ const consent = value;
212
+ return typeof consent.version === "string" && consent.version.length > 0 && consent.version.length <= 80 && Number.isSafeInteger(consent.acceptedAt) && consent.acceptedAt > 0;
213
+ }
214
+ function hasAssistantAvatarEngineConsent(value) {
215
+ return validAssistantAvatarEngineConsent(value) && value.version === "2026-10-08.1";
216
+ }
217
+ //#endregion
218
+ //#region src/shared/assistant-avatar.ts
219
+ const ASSISTANT_AVATAR_STATES = [
220
+ "idle",
221
+ "listening",
222
+ "thinking",
223
+ "speaking",
224
+ "waiting",
225
+ "error"
226
+ ];
227
+ const ASSISTANT_AVATAR_RUNTIME_PATH = "/api/codingns/assistant-avatar-runtime";
228
+ const BUILTIN_ASSISTANT_AVATAR = {
229
+ id: "codingns-default",
230
+ name: "鱼妞",
231
+ renderer: "builtin",
232
+ source: "",
233
+ spriteVersion: 2
234
+ };
235
+ /** 保留旧默认 ID 和存储字段;实际生图素材通过固定的同源 URL 加载。 */
236
+ const BUILTIN_ASSISTANT_AVATARS = [BUILTIN_ASSISTANT_AVATAR, {
237
+ id: "codingns-basic-male",
238
+ name: "鱼仔",
239
+ renderer: "builtin",
240
+ source: "",
241
+ spriteVersion: 2
242
+ }];
243
+ /** 旧名称仅用于写入兼容;读取归一化后统一显示新名称,其他内置字段仍不可替换。 */
244
+ const legacyBuiltinNames = {
245
+ "codingns-default": "CodingNS",
246
+ "codingns-basic-male": "基础形象 · 男生"
247
+ };
248
+ const BUILTIN_ASSISTANT_AVATAR_SOURCES = {
249
+ "codingns-default": "/api/codingns/assistant-avatar-basic/female-v1.png",
250
+ "codingns-basic-male": "/api/codingns/assistant-avatar-basic/male-v1.png"
251
+ };
252
+ function getBuiltinAssistantAvatar(id) {
253
+ return BUILTIN_ASSISTANT_AVATARS.find((model) => model.id === id);
254
+ }
255
+ function builtinAssistantAvatarLabelKey(id) {
256
+ if (id === BUILTIN_ASSISTANT_AVATAR.id) return "avatar.builtinName";
257
+ if (id === "codingns-basic-male") return "avatar.builtinMaleName";
258
+ }
259
+ BUILTIN_ASSISTANT_AVATAR.id;
260
+ /** 资源只允许网页地址;任意脚本、file: 和协议相对地址均不能作为素材入口。 */
261
+ function isAssistantAvatarSource(value) {
262
+ if (value.length === 0 || value.length > 2048 || /[\s\\\u0000-\u001f]/u.test(value)) return false;
263
+ if (value.startsWith("/") && !value.startsWith("//")) return true;
264
+ try {
265
+ const url = new URL(value);
266
+ return (url.protocol === "https:" || url.protocol === "http:") && url.username === "" && url.password === "";
267
+ } catch {
268
+ return false;
269
+ }
270
+ }
271
+ /** 读取旧设置时补默认值;非法模型被过滤,绝不让坏配置阻止助理入口。 */
272
+ function normalizeAssistantAppearance(value) {
273
+ const record = asRecord(value);
274
+ const models = [...BUILTIN_ASSISTANT_AVATARS];
275
+ const seen = new Set(models.map((model) => model.id));
276
+ for (const item of Array.isArray(record.models) ? record.models.slice(0, 21) : []) {
277
+ if (models.length >= 21) break;
278
+ const model = asRecord(item);
279
+ if (!validModel(model) || seen.has(model.id)) continue;
280
+ seen.add(model.id);
281
+ models.push(copyAssistantAvatarModel(model));
282
+ }
283
+ return {
284
+ floatingEnabled: record.floatingEnabled === true,
285
+ dialogEnabled: record.dialogEnabled !== false,
286
+ floatingSize: boundedSize(record.floatingSize, 72, 320, 144),
287
+ dialogSize: boundedSize(record.dialogSize, 120, 480, 240),
288
+ selectedId: typeof record.selectedId === "string" && (seen.has(record.selectedId) || getAssistantAvatarPreset(record.selectedId) !== void 0) ? record.selectedId : BUILTIN_ASSISTANT_AVATAR.id,
289
+ models,
290
+ ...validAssistantAvatarConsent(record.thirdPartyConsent) ? { thirdPartyConsent: {
291
+ version: record.thirdPartyConsent.version,
292
+ acceptedAt: record.thirdPartyConsent.acceptedAt
293
+ } } : {},
294
+ ...validAssistantAvatarEngineConsent(record.engineConsent) ? { engineConsent: {
295
+ version: record.engineConsent.version,
296
+ acceptedAt: record.engineConsent.acceptedAt
297
+ } } : {}
298
+ };
299
+ }
300
+ /** 写入必须严格校验,不能把被静默修正的数据报告为保存成功。 */
301
+ function validateAssistantAppearance(value) {
302
+ const record = asRecord(value);
303
+ if (record.thirdPartyConsent !== void 0 && !validAssistantAvatarConsent(record.thirdPartyConsent)) throw new TypeError("第三方形象协议记录无效");
304
+ if (record.engineConsent !== void 0 && !validAssistantAvatarEngineConsent(record.engineConsent)) throw new TypeError("Live2D 引擎许可记录无效");
305
+ if (typeof record.floatingEnabled !== "boolean" || typeof record.dialogEnabled !== "boolean") throw new TypeError("形象显示开关无效");
306
+ for (const [key, min, max] of [[
307
+ "floatingSize",
308
+ 72,
309
+ 320
310
+ ], [
311
+ "dialogSize",
312
+ 120,
313
+ 480
314
+ ]]) if (typeof record[key] !== "number" || !Number.isInteger(record[key]) || record[key] < min || record[key] > max) throw new TypeError("形象尺寸超出范围");
315
+ if (!Array.isArray(record.models) || record.models.length === 0 || record.models.length > 21) throw new TypeError(`形象清单必须包含 1 到 21 项`);
316
+ const seen = /* @__PURE__ */ new Set();
317
+ for (const item of record.models) {
318
+ const model = asRecord(item);
319
+ validateAssistantAvatarModel(model);
320
+ if (typeof model.id !== "string" || seen.has(model.id)) throw new TypeError("形象 ID 重复或无效");
321
+ seen.add(model.id);
322
+ }
323
+ if (!seen.has(BUILTIN_ASSISTANT_AVATAR.id) || typeof record.selectedId !== "string" || !seen.has(record.selectedId) && getAssistantAvatarPreset(record.selectedId) === void 0) throw new TypeError("当前形象不在清单中");
324
+ }
325
+ /** 所有导入和管理写入共享同一校验,不能由某个适配器绕过资源边界。 */
326
+ function validateAssistantAvatarModel(value) {
327
+ const model = asRecord(value);
328
+ const builtin = getBuiltinAssistantAvatar(model.id);
329
+ if (builtin !== void 0) {
330
+ const keys = Object.keys(builtin);
331
+ if (keys.some((key) => model[key] !== builtin[key] && !(key === "name" && model[key] === legacyBuiltinNames[builtin.id])) || Object.keys(model).some((key) => !keys.includes(key) && model[key] !== void 0)) throw new TypeError("内置形象不可替换");
332
+ return;
333
+ }
334
+ if (!validModel(model)) throw new TypeError("形象名称、类型或资源地址无效");
335
+ }
336
+ /** 保留旧存储字段,复制白名单内的数据,避免扩展把运行对象写进设置。 */
337
+ function copyAssistantAvatarModel(model) {
338
+ return {
339
+ id: model.id,
340
+ name: model.name.trim(),
341
+ ...copyAsset(model),
342
+ ...model.package === void 0 ? {} : { package: { ...model.package } },
343
+ ...model.surfaces === void 0 ? {} : { surfaces: Object.fromEntries(Object.entries(model.surfaces).map(([surface, asset]) => [surface, copyAsset(asset)])) }
344
+ };
345
+ }
346
+ function selectedAssistantAvatar(appearance) {
347
+ return appearance.models.find((model) => model.id === appearance.selectedId) ?? getAssistantAvatarPreset(appearance.selectedId)?.model ?? BUILTIN_ASSISTANT_AVATAR;
348
+ }
349
+ /** 创建表单只列已登记形象;已创建助理和管理器可保留旧选择的兼容入口。 */
350
+ function listAssistantAvatars(appearance, includeLegacy = true) {
351
+ if (!includeLegacy) return appearance.models;
352
+ const legacy = getAssistantAvatarPreset(appearance.selectedId)?.model;
353
+ return legacy === void 0 || appearance.models.some((model) => model.id === legacy.id) ? appearance.models : [...appearance.models, legacy];
354
+ }
355
+ /** 展示位置只覆盖素材,不覆盖身份;状态映射也跟随所选素材,不串用另一种格式。 */
356
+ function resolveAssistantAvatarAsset(model, surface) {
357
+ const asset = model.surfaces?.[surface];
358
+ return asset === void 0 ? model : {
359
+ id: model.id,
360
+ name: model.name,
361
+ ...copyAsset(asset),
362
+ ...model.package === void 0 ? {} : { package: model.package },
363
+ ...model.surfaces === void 0 ? {} : { surfaces: model.surfaces }
364
+ };
365
+ }
366
+ function assistantAvatarImageSource(model, state) {
367
+ return model.stateSources?.[state] ?? model.source;
368
+ }
369
+ function resolveAssistantAvatarState(state, pending = false) {
370
+ if (state === "error") return "error";
371
+ if (state === "speaking") return "speaking";
372
+ if (pending || state === "loading" || state === "thinking") return "thinking";
373
+ if (state === "listening" || state === "recording") return "listening";
374
+ if (state === "waiting") return "waiting";
375
+ return "idle";
376
+ }
377
+ /** 帧布局来自 Signalight/codex-to-dsh-pet;单帧 192×208,8 列。 */
378
+ function assistantSpriteMotion(state) {
379
+ if (state === "error") return {
380
+ row: 5,
381
+ frames: 8,
382
+ interval: 140
383
+ };
384
+ if (state === "waiting" || state === "listening") return {
385
+ row: 6,
386
+ frames: 6,
387
+ interval: 150
388
+ };
389
+ if (state === "thinking") return {
390
+ row: 7,
391
+ frames: 6,
392
+ interval: 120
393
+ };
394
+ if (state === "speaking") return {
395
+ row: 8,
396
+ frames: 6,
397
+ interval: 150
398
+ };
399
+ return {
400
+ row: 0,
401
+ frames: 6,
402
+ interval: 160
403
+ };
404
+ }
405
+ /** 视口变化或旧坐标失效时让宠物保持在可点击范围。 */
406
+ function clampAssistantAvatarPosition(x, y, width, height, viewportWidth, viewportHeight) {
407
+ return {
408
+ x: Math.max(0, Math.min(Number.isFinite(x) ? x : 0, Math.max(0, viewportWidth - width))),
409
+ y: Math.max(0, Math.min(Number.isFinite(y) ? y : 0, Math.max(0, viewportHeight - height)))
410
+ };
411
+ }
412
+ function validModel(model) {
413
+ return typeof model.id === "string" && /^[A-Za-z][A-Za-z0-9_-]{0,79}$/u.test(model.id) && typeof model.name === "string" && model.name.trim().length > 0 && model.name.length <= 80 && validAsset(model) && validPackage(model.package) && validSurfaces(model.surfaces);
414
+ }
415
+ function validAsset(asset) {
416
+ return typeof asset.renderer === "string" && validId(asset.renderer) && asset.renderer !== "builtin" && typeof asset.source === "string" && isAssistantAvatarSource(asset.source) && (asset.spriteVersion === 1 || asset.spriteVersion === 2) && validStateMap(asset.stateSources, (source) => asset.renderer === "image" && isAssistantAvatarSource(source)) && validStateMap(asset.motionGroups, (group) => asset.renderer === "live2d" && group.trim().length > 0 && group.length <= 120) && validLive2dOptions(asset.live2d, asset.renderer);
417
+ }
418
+ function validStateMap(value, valid) {
419
+ return value === void 0 || isRecord(value) && Object.entries(value).every(([state, item]) => ASSISTANT_AVATAR_STATES.includes(state) && typeof item === "string" && valid(item));
420
+ }
421
+ function validPackage(value) {
422
+ if (value === void 0) return true;
423
+ if (!isRecord(value) || typeof value.adapterId !== "string" || !validId(value.adapterId)) return false;
424
+ for (const key of ["manifestUrl", "homepage"]) if (value[key] !== void 0 && (typeof value[key] !== "string" || !isAssistantAvatarSource(value[key]))) return false;
425
+ for (const key of ["author", "license"]) if (value[key] !== void 0 && (typeof value[key] !== "string" || value[key].length > 2048)) return false;
426
+ if (value.installationId !== void 0 && (typeof value.installationId !== "string" || !/^[a-f0-9]{64}$/u.test(value.installationId))) return false;
427
+ return Object.keys(value).every((key) => [
428
+ "adapterId",
429
+ "manifestUrl",
430
+ "homepage",
431
+ "author",
432
+ "license",
433
+ "installationId"
434
+ ].includes(key));
435
+ }
436
+ function validSurfaces(value) {
437
+ return value === void 0 || isRecord(value) && Object.entries(value).every(([surface, asset]) => ["floating", "dialog"].includes(surface) && isRecord(asset) && validAsset(asset));
438
+ }
439
+ function validLive2dOptions(value, renderer) {
440
+ if (value === void 0) return true;
441
+ if (!isRecord(value) || renderer !== "live2d" || Object.keys(value).some((key) => !["scale", "position"].includes(key))) return false;
442
+ if (value.scale !== void 0 && (typeof value.scale !== "number" || !Number.isFinite(value.scale) || value.scale < .05 || value.scale > 10)) return false;
443
+ return value.position === void 0 || Array.isArray(value.position) && value.position.length === 2 && value.position.every((item) => typeof item === "number" && Number.isFinite(item) && Math.abs(item) <= 2);
444
+ }
445
+ function copyAsset(asset) {
446
+ return {
447
+ renderer: asset.renderer,
448
+ source: asset.source,
449
+ spriteVersion: asset.spriteVersion,
450
+ ...asset.stateSources === void 0 ? {} : { stateSources: { ...asset.stateSources } },
451
+ ...asset.motionGroups === void 0 ? {} : { motionGroups: { ...asset.motionGroups } },
452
+ ...asset.live2d === void 0 ? {} : { live2d: {
453
+ ...asset.live2d,
454
+ ...asset.live2d.position === void 0 ? {} : { position: [...asset.live2d.position] }
455
+ } }
456
+ };
457
+ }
458
+ function validId(value) {
459
+ return /^[A-Za-z][A-Za-z0-9_-]{0,79}$/u.test(value);
460
+ }
461
+ function isRecord(value) {
462
+ return typeof value === "object" && value !== null && !Array.isArray(value);
463
+ }
464
+ function boundedSize(value, min, max, fallback) {
465
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(min, Math.min(max, Math.round(value))) : fallback;
466
+ }
467
+ function asRecord(value) {
468
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
469
+ }
470
+ //#endregion
471
+ //#region src/shared/assistant-avatar-adapters.ts
472
+ /** 相对路径以清单实际响应 URL 为基准,重定向后不再使用旧目录。 */
473
+ function resolveAssistantAvatarSource(source, manifestUrl) {
474
+ if (typeof source !== "string" || source.trim().length === 0 || source.trim().startsWith("//") || /[\\\u0000-\u001f]/u.test(source)) throw new TypeError("形象包资源地址无效");
475
+ if (!isAssistantAvatarSource(manifestUrl)) throw new TypeError("形象包清单地址无效");
476
+ const resolved = new URL(source, manifestUrl).href;
477
+ if (!isAssistantAvatarSource(resolved)) throw new TypeError("形象包资源地址无效");
478
+ return resolved;
479
+ }
480
+ function record(value) {
481
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("形象包清单必须是 JSON 对象");
482
+ return value;
483
+ }
484
+ function looksLike(value, key) {
485
+ return typeof value === "object" && value !== null && key in value;
486
+ }
487
+ function asset(value, url) {
488
+ const input = record(value);
489
+ return {
490
+ renderer: input.renderer,
491
+ source: resolveAssistantAvatarSource(input.source, url),
492
+ spriteVersion: input.spriteVersion ?? 2,
493
+ ...input.stateSources === void 0 ? {} : { stateSources: Object.fromEntries(Object.entries(record(input.stateSources)).map(([state, source]) => [state, resolveAssistantAvatarSource(source, url)])) },
494
+ ...input.motionGroups === void 0 ? {} : { motionGroups: record(input.motionGroups) },
495
+ ...input.live2d === void 0 ? {} : { live2d: record(input.live2d) }
496
+ };
497
+ }
498
+ function build(input, base, context, adapterId, surfaces) {
499
+ const info = {
500
+ adapterId,
501
+ manifestUrl: context.manifestUrl,
502
+ ...input.author === void 0 ? {} : { author: input.author },
503
+ ...input.license === void 0 ? {} : { license: input.license },
504
+ ...input.homepage === void 0 ? {} : { homepage: resolveAssistantAvatarSource(input.homepage, context.manifestUrl) }
505
+ };
506
+ const model = {
507
+ id: input.id,
508
+ name: input.name ?? input.displayName,
509
+ ...base,
510
+ package: info,
511
+ ...surfaces === void 0 ? {} : { surfaces }
512
+ };
513
+ validateAssistantAvatarModel(model);
514
+ return copyAssistantAvatarModel(model);
515
+ }
516
+ const BUILTIN_ASSISTANT_AVATAR_ADAPTERS = [
517
+ {
518
+ id: "codingns-pack",
519
+ labelKey: "avatar.adapter.native",
520
+ matches: (value) => looksLike(value, "avatarManifestVersion"),
521
+ parse(value, context) {
522
+ const input = record(value);
523
+ if (input.avatarManifestVersion !== 1) throw new TypeError("不支持的 CodingNS 形象包版本");
524
+ const surfaces = input.surfaces === void 0 ? void 0 : Object.fromEntries(Object.entries(record(input.surfaces)).map(([surface, source]) => [surface, asset(source, context.manifestUrl)]));
525
+ return build(input, asset(input.asset, context.manifestUrl), context, this.id, surfaces);
526
+ }
527
+ },
528
+ {
529
+ id: "codex-pet",
530
+ labelKey: "avatar.adapter.codex",
531
+ matches: (value) => looksLike(value, "spritesheetPath"),
532
+ parse(value, context) {
533
+ const input = record(value);
534
+ return build(input, {
535
+ renderer: "spritesheet",
536
+ source: resolveAssistantAvatarSource(input.spritesheetPath, context.manifestUrl),
537
+ spriteVersion: input.spriteVersionNumber
538
+ }, context, this.id);
539
+ }
540
+ },
541
+ {
542
+ id: "dsh-live2d-pet",
543
+ labelKey: "avatar.adapter.dsh",
544
+ matches: (value) => looksLike(value, "petManifestVersion"),
545
+ parse(value, context) {
546
+ const input = record(value);
547
+ if (input.petManifestVersion !== 2 || input.renderer !== "live2d") throw new TypeError("不支持的 DSH 形象包格式");
548
+ const live2d = record(input.live2d);
549
+ const translate = live2d.translate === void 0 ? void 0 : record(live2d.translate);
550
+ const motions = live2d.motions === void 0 ? {} : record(live2d.motions);
551
+ const motionGroups = Object.fromEntries(Object.entries({
552
+ idle: ["idle"],
553
+ thinking: ["thinking", "tool"],
554
+ speaking: ["speaking"],
555
+ listening: ["listening", "waiting"],
556
+ waiting: [
557
+ "waiting",
558
+ "asking",
559
+ "queued"
560
+ ],
561
+ error: ["error", "failed"]
562
+ }).flatMap(([state, aliases]) => {
563
+ const group = aliases.map((key) => motions[key]).find((item) => item !== void 0);
564
+ return group === void 0 ? [] : [[state, group]];
565
+ }));
566
+ return build(input, {
567
+ renderer: "live2d",
568
+ source: resolveAssistantAvatarSource(live2d.model, context.manifestUrl),
569
+ spriteVersion: 2,
570
+ ...Object.keys(motionGroups).length === 0 ? {} : { motionGroups },
571
+ ...live2d.scale === void 0 && translate === void 0 ? {} : { live2d: {
572
+ ...live2d.scale === void 0 ? {} : { scale: live2d.scale },
573
+ ...translate === void 0 ? {} : { position: [translate.x, translate.y] }
574
+ } }
575
+ }, context, this.id);
576
+ }
577
+ },
578
+ {
579
+ id: "cubism-model",
580
+ labelKey: "avatar.adapter.cubism",
581
+ matches: (value) => looksLike(value, "FileReferences") || looksLike(value, "model"),
582
+ parse(value, context) {
583
+ const input = record(value);
584
+ const refs = input.FileReferences === void 0 ? input : record(input.FileReferences);
585
+ const textures = refs.Textures ?? refs.textures;
586
+ if (input.FileReferences !== void 0 && input.Version !== 3 || !Array.isArray(textures) || textures.length === 0) throw new TypeError("Live2D 模型清单不完整");
587
+ resolveAssistantAvatarSource(refs.Moc ?? refs.model, context.manifestUrl);
588
+ for (const texture of textures) resolveAssistantAvatarSource(texture, context.manifestUrl);
589
+ let hash = 2166136261;
590
+ for (const character of context.manifestUrl) hash = Math.imul(hash ^ character.charCodeAt(0), 16777619);
591
+ const filename = new URL(context.manifestUrl).pathname.split("/").pop() ?? "Live2D";
592
+ return build({
593
+ id: `live2d-${(hash >>> 0).toString(36)}`,
594
+ name: decodeURIComponent(filename).replace(/\.model3?\.json$/iu, "").slice(0, 80) || "Live2D"
595
+ }, {
596
+ renderer: "live2d",
597
+ source: context.manifestUrl,
598
+ spriteVersion: 2
599
+ }, context, this.id);
600
+ }
601
+ }
602
+ ];
603
+ const ASSISTANT_AVATAR_STATUS_PATH = "/api/codingns/assistant-avatar-cache-status";
604
+ //#endregion
605
+ //#region src/shared/assistant-tts.ts
606
+ const DEFAULT_ASSISTANT_TTS_PARAMETERS = Object.freeze({
607
+ rate: 1,
608
+ volume: 1,
609
+ segmentPauseMs: 0,
610
+ chunkTokens: 75,
611
+ seed: null
612
+ });
613
+ const ASSISTANT_TTS_PARAMETER_LIMITS = {
614
+ rate: {
615
+ min: .5,
616
+ max: 2,
617
+ step: .05
618
+ },
619
+ volume: {
620
+ min: 0,
621
+ max: 1,
622
+ step: .01
623
+ },
624
+ segmentPauseMs: {
625
+ min: 0,
626
+ max: 2e3,
627
+ step: 50
628
+ },
629
+ chunkTokens: {
630
+ min: 30,
631
+ max: 120,
632
+ step: 1
633
+ },
634
+ seed: {
635
+ min: 0,
636
+ max: 4294967295,
637
+ step: 1
638
+ }
639
+ };
640
+ /** 已保存的旧值或损坏值用默认值恢复;写入及试听请求使用严格校验。 */
641
+ function readAssistantTtsParameters(value) {
642
+ const result = { ...DEFAULT_ASSISTANT_TTS_PARAMETERS };
643
+ for (const key of [
644
+ "rate",
645
+ "volume",
646
+ "segmentPauseMs",
647
+ "chunkTokens",
648
+ "seed"
649
+ ]) {
650
+ const item = value?.[key];
651
+ if (validParameter(key, item)) Object.assign(result, { [key]: item });
652
+ }
653
+ return result;
654
+ }
655
+ function validateAssistantTtsParameters(value, base) {
656
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("播报参数必须为对象");
657
+ const result = readAssistantTtsParameters(base);
658
+ for (const [key, item] of Object.entries(value)) {
659
+ if (!Object.hasOwn(ASSISTANT_TTS_PARAMETER_LIMITS, key)) throw new Error(`不支持的播报参数:${key}`);
660
+ const name = key;
661
+ if (!validParameter(name, item)) {
662
+ const limit = ASSISTANT_TTS_PARAMETER_LIMITS[name];
663
+ throw new Error(`播报参数 ${key} 需要 ${limit.min}~${limit.max}${name === "seed" ? " 的整数或 null" : name === "chunkTokens" || name === "segmentPauseMs" ? " 的整数" : ""}`);
664
+ }
665
+ Object.assign(result, { [key]: item });
666
+ }
667
+ return result;
668
+ }
669
+ function validParameter(key, value) {
670
+ if (key === "seed" && value === null) return true;
671
+ const limit = ASSISTANT_TTS_PARAMETER_LIMITS[key];
672
+ return typeof value === "number" && Number.isFinite(value) && value >= limit.min && value <= limit.max && (key === "rate" || key === "volume" || Number.isInteger(value));
673
+ }
674
+ const DEFAULT_ASSISTANT_TTS_SETTINGS = {
675
+ backend: "browser",
676
+ selectedId: "moss:Junhao",
677
+ voices: [],
678
+ parameters: DEFAULT_ASSISTANT_TTS_PARAMETERS
679
+ };
680
+ const MOSS_BUILTIN_VOICES = [
681
+ {
682
+ id: "moss:Junhao",
683
+ name: "Junhao · 欢迎关注模思智能",
684
+ language: "zh",
685
+ gender: "male",
686
+ kind: "builtin",
687
+ reference: "Junhao",
688
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
689
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
690
+ },
691
+ {
692
+ id: "moss:Zhiming",
693
+ name: "Zhiming · 京味胡同闲聊",
694
+ language: "zh",
695
+ gender: "male",
696
+ kind: "builtin",
697
+ reference: "Zhiming",
698
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
699
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
700
+ },
701
+ {
702
+ id: "moss:Weiguo",
703
+ name: "Weiguo · 说书",
704
+ language: "zh",
705
+ gender: "male",
706
+ kind: "builtin",
707
+ reference: "Weiguo",
708
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
709
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
710
+ },
711
+ {
712
+ id: "moss:Xiaoyu",
713
+ name: "Xiaoyu · 明星",
714
+ language: "zh",
715
+ gender: "female",
716
+ kind: "builtin",
717
+ reference: "Xiaoyu",
718
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
719
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
720
+ },
721
+ {
722
+ id: "moss:Yuewen",
723
+ name: "Yuewen · 机车",
724
+ language: "zh",
725
+ gender: "female",
726
+ kind: "builtin",
727
+ reference: "Yuewen",
728
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
729
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
730
+ },
731
+ {
732
+ id: "moss:Lingyu",
733
+ name: "Lingyu · 深夜电台",
734
+ language: "zh",
735
+ gender: "female",
736
+ kind: "builtin",
737
+ reference: "Lingyu",
738
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
739
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
740
+ },
741
+ {
742
+ id: "moss:Trump",
743
+ name: "Trump · Trump",
744
+ language: "en",
745
+ gender: "male",
746
+ kind: "builtin",
747
+ reference: "Trump",
748
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
749
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
750
+ },
751
+ {
752
+ id: "moss:Ava",
753
+ name: "Ava · The Bitter Lesson",
754
+ language: "en",
755
+ gender: "female",
756
+ kind: "builtin",
757
+ reference: "Ava",
758
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
759
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
760
+ },
761
+ {
762
+ id: "moss:Bella",
763
+ name: "Bella · A Gentle Reminder",
764
+ language: "en",
765
+ gender: "female",
766
+ kind: "builtin",
767
+ reference: "Bella",
768
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
769
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
770
+ },
771
+ {
772
+ id: "moss:Adam",
773
+ name: "Adam · English News",
774
+ language: "en",
775
+ gender: "male",
776
+ kind: "builtin",
777
+ reference: "Adam",
778
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
779
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
780
+ },
781
+ {
782
+ id: "moss:Nathan",
783
+ name: "Nathan · The Quiet Motion of the World",
784
+ language: "en",
785
+ gender: "male",
786
+ kind: "builtin",
787
+ reference: "Nathan",
788
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
789
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
790
+ },
791
+ {
792
+ id: "moss:Soyo",
793
+ name: "Soyo · Soyo",
794
+ language: "ja",
795
+ gender: "female",
796
+ kind: "builtin",
797
+ reference: "Soyo",
798
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
799
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
800
+ },
801
+ {
802
+ id: "moss:Saki",
803
+ name: "Saki · Saki",
804
+ language: "ja",
805
+ gender: "female",
806
+ kind: "builtin",
807
+ reference: "Saki",
808
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
809
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
810
+ },
811
+ {
812
+ id: "moss:Mortis",
813
+ name: "Mortis · Mortis",
814
+ language: "ja",
815
+ gender: "female",
816
+ kind: "builtin",
817
+ reference: "Mortis",
818
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
819
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
820
+ },
821
+ {
822
+ id: "moss:Umiri",
823
+ name: "Umiri · Umiri",
824
+ language: "ja",
825
+ gender: "female",
826
+ kind: "builtin",
827
+ reference: "Umiri",
828
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
829
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
830
+ },
831
+ {
832
+ id: "moss:Mei",
833
+ name: "Mei · Togawa",
834
+ language: "ja",
835
+ gender: "female",
836
+ kind: "builtin",
837
+ reference: "Mei",
838
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
839
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
840
+ },
841
+ {
842
+ id: "moss:Anon",
843
+ name: "Anon · Anon",
844
+ language: "ja",
845
+ gender: "female",
846
+ kind: "builtin",
847
+ reference: "Anon",
848
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
849
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
850
+ },
851
+ {
852
+ id: "moss:Arisa",
853
+ name: "Arisa · Arisa",
854
+ language: "ja",
855
+ gender: "female",
856
+ kind: "builtin",
857
+ reference: "Arisa",
858
+ source: "https://huggingface.co/OpenMOSS-Team/MOSS-TTS-Nano-100M-ONNX/blob/f52645cb467506d8e18e746ddd59482685b74e58/browser_poc_manifest.json",
859
+ license: "Apache-2.0(项目许可;参考声音授权另行确认)"
860
+ }
861
+ ];
862
+ const ASSISTANT_TTS_PATH = "/api/codingns/assistant/voice/tts";
863
+ const ASSISTANT_VOICE_SITES = [{
864
+ id: "kyutai",
865
+ name: "Kyutai 开放音色",
866
+ url: "https://kyutai.org/tts/",
867
+ help: "试听后填写 kyutai:voice-donations/0a67.wav 等录音 ID,或 Hugging Face 音频文件网址。"
868
+ }, {
869
+ id: "aishell",
870
+ name: "AISHELL-3 中文录音",
871
+ url: "https://huggingface.co/datasets/AISHELL/AISHELL-3",
872
+ help: "填写 aishell:完整录音ID,或 Hugging Face 中单个音频文件的网址。"
873
+ }];
874
+ /** 旧设置只补默认值,内置音色始终由目录提供,不能被自定义记录覆盖。 */
875
+ function readAssistantTtsSettings(value) {
876
+ const voices = (value?.voices ?? []).filter((voice) => voice.kind === "reference" && /^ref-[a-f0-9]{64}$/u.test(voice.id) && voice.reference === voice.id);
877
+ const selectedId = value?.selectedId ?? DEFAULT_ASSISTANT_TTS_SETTINGS.selectedId;
878
+ return {
879
+ backend: value?.backend === "moss-onnx" ? "moss-onnx" : "browser",
880
+ selectedId: [...MOSS_BUILTIN_VOICES, ...voices].some((voice) => voice.id === selectedId) ? selectedId : DEFAULT_ASSISTANT_TTS_SETTINGS.selectedId,
881
+ voices,
882
+ parameters: readAssistantTtsParameters(value?.parameters)
883
+ };
884
+ }
885
+ //#endregion
886
+ //#region src/client/global-voice-runtime-registry.ts
887
+ const adapters = /* @__PURE__ */ new WeakMap();
888
+ function registerGlobalVoiceAdapter(services, adapter) {
889
+ adapters.set(services, adapter);
890
+ return () => {
891
+ if (adapters.get(services) === adapter) adapters.delete(services);
892
+ };
893
+ }
894
+ function getGlobalVoiceAdapter(services) {
895
+ return adapters.get(services);
896
+ }
897
+ //#endregion
898
+ //#region src/client/avatar/image.ts
899
+ /** 图片差分也消费相同的状态接口;某张差分缺失时先回落该角色默认图片。 */
900
+ function ImageAssistantAvatar({ model, state, size, onError, onLoadProgress }) {
901
+ const requested = assistantAvatarImageSource(model, state);
902
+ const [failed, setFailed] = (0, react.useState)("");
903
+ const image = (0, react.useRef)(null);
904
+ (0, react.useEffect)(() => setFailed(""), [requested]);
905
+ const source = failed === requested ? model.source : requested;
906
+ (0, react.useEffect)(() => {
907
+ onLoadProgress?.({ phase: image.current?.complete && image.current.naturalWidth > 0 ? "ready" : "resources" });
908
+ }, [source, onLoadProgress]);
909
+ return (0, react.createElement)("img", {
910
+ key: source,
911
+ ref: image,
912
+ src: source,
913
+ alt: "",
914
+ draggable: false,
915
+ onLoad: () => onLoadProgress?.({ phase: "ready" }),
916
+ onError: () => {
917
+ if (source !== model.source) setFailed(requested);
918
+ else onError("avatar_image_load_failed");
919
+ },
920
+ style: {
921
+ display: "block",
922
+ width: size,
923
+ maxWidth: "100%",
924
+ height: size * 208 / 192,
925
+ objectFit: "contain"
926
+ }
927
+ });
928
+ }
929
+ //#endregion
930
+ //#region src/client/avatar/builtin.ts
931
+ /** 基础形象使用生图工具生成的透明 PNG,不手绘角色,也不加载第三方引擎。 */
932
+ function BuiltinAssistantAvatar(props) {
933
+ const source = BUILTIN_ASSISTANT_AVATAR_SOURCES[props.model.id] ?? BUILTIN_ASSISTANT_AVATAR_SOURCES[BUILTIN_ASSISTANT_AVATAR.id];
934
+ return (0, react.createElement)(ImageAssistantAvatar, {
935
+ ...props,
936
+ model: {
937
+ ...props.model,
938
+ source
939
+ }
940
+ });
941
+ }
942
+ //#endregion
943
+ //#region src/client/avatar/spritesheet.ts
944
+ /** 可测试的时钟:只推进当前动作实际拥有的帧,暂停后不追赶后台时间。 */
945
+ var AssistantSpriteClock = class {
946
+ state;
947
+ frame = 0;
948
+ last;
949
+ constructor(state) {
950
+ this.state = state;
951
+ }
952
+ setState(state) {
953
+ if (state !== this.state) {
954
+ this.state = state;
955
+ this.frame = 0;
956
+ this.last = void 0;
957
+ }
958
+ }
959
+ reset() {
960
+ this.last = void 0;
961
+ }
962
+ tick(now) {
963
+ const motion = assistantSpriteMotion(this.state);
964
+ if (this.last === void 0) this.last = now;
965
+ const steps = Math.floor(Math.max(0, now - this.last) / motion.interval);
966
+ this.frame = (this.frame + steps) % motion.frames;
967
+ this.last += steps * motion.interval;
968
+ return {
969
+ row: motion.row,
970
+ frame: this.frame
971
+ };
972
+ }
973
+ };
974
+ /** 参考 Signalight 的纯 DOM 图集渲染,不引入 Pixi 或上游会话逻辑。 */
975
+ function SpriteSheetAssistantAvatar({ model, state, size, onError, onLoadProgress }) {
976
+ const element = (0, react.useRef)(null);
977
+ const stateRef = (0, react.useRef)(state);
978
+ stateRef.current = state;
979
+ (0, react.useEffect)(() => {
980
+ if (element.current !== null) element.current.style.backgroundPosition = `0% ${assistantSpriteMotion(state).row * 100 / (model.spriteVersion === 2 ? 10 : 8)}%`;
981
+ }, [state, model.spriteVersion]);
982
+ (0, react.useEffect)(() => {
983
+ const target = element.current;
984
+ if (target === null) return void 0;
985
+ onLoadProgress?.({ phase: "resources" });
986
+ const image = new Image();
987
+ let disposed = false;
988
+ let frameId = 0;
989
+ const clock = new AssistantSpriteClock(stateRef.current);
990
+ const media = window.matchMedia?.("(prefers-reduced-motion: reduce)");
991
+ const rows = model.spriteVersion === 2 ? 11 : 9;
992
+ const paint = (now) => {
993
+ clock.setState(stateRef.current);
994
+ const frame = clock.tick(now);
995
+ target.style.backgroundPosition = `${frame.frame * 100 / 7}% ${frame.row * 100 / (rows - 1)}%`;
996
+ };
997
+ const tick = (now) => {
998
+ if (disposed) return;
999
+ paint(now);
1000
+ frameId = requestAnimationFrame(tick);
1001
+ };
1002
+ const refresh = () => {
1003
+ cancelAnimationFrame(frameId);
1004
+ clock.reset();
1005
+ paint(performance.now());
1006
+ if (!disposed && !document.hidden && !media?.matches) frameId = requestAnimationFrame(tick);
1007
+ };
1008
+ image.onload = () => {
1009
+ if (disposed) return;
1010
+ if (image.naturalWidth !== 1536 || image.naturalHeight !== 208 * rows) {
1011
+ onError(`avatar_sprite_dimensions_invalid: expected 1536x${208 * rows}`);
1012
+ return;
1013
+ }
1014
+ target.style.backgroundImage = `url(${JSON.stringify(model.source)})`;
1015
+ onLoadProgress?.({
1016
+ phase: "ready",
1017
+ loaded: 1,
1018
+ total: 1
1019
+ });
1020
+ refresh();
1021
+ };
1022
+ image.onerror = () => {
1023
+ if (!disposed) onError("avatar_sprite_load_failed");
1024
+ };
1025
+ image.src = model.source;
1026
+ document.addEventListener("visibilitychange", refresh);
1027
+ media?.addEventListener("change", refresh);
1028
+ return () => {
1029
+ disposed = true;
1030
+ image.onload = null;
1031
+ image.onerror = null;
1032
+ cancelAnimationFrame(frameId);
1033
+ document.removeEventListener("visibilitychange", refresh);
1034
+ media?.removeEventListener("change", refresh);
1035
+ };
1036
+ }, [
1037
+ model.source,
1038
+ model.spriteVersion,
1039
+ onError,
1040
+ onLoadProgress
1041
+ ]);
1042
+ return (0, react.createElement)("div", {
1043
+ ref: element,
1044
+ "aria-hidden": true,
1045
+ style: {
1046
+ width: size,
1047
+ aspectRatio: "192 / 208",
1048
+ maxWidth: "100%",
1049
+ backgroundRepeat: "no-repeat",
1050
+ backgroundSize: `800% ${model.spriteVersion === 2 ? 1100 : 900}%`
1051
+ }
1052
+ });
1053
+ }
1054
+ //#endregion
1055
+ //#region src/client/avatar/cache-status.ts
1056
+ /** 只诊断本站固定预设;查询失败不阻断模型加载,不推测浏览器是否命中缓存。 */
1057
+ async function readAssistantAvatarCacheStatus(source, location, fetchStatus = (...args) => fetch(...args)) {
1058
+ try {
1059
+ const origin = new URL(location);
1060
+ const model = new URL(source, origin);
1061
+ const installedId = model.origin === origin.origin && model.pathname === "/api/codingns/assistant-avatar-assets" ? model.searchParams.get("pack") : null;
1062
+ if (installedId !== null && /^[a-f0-9]{64}$/u.test(installedId)) {
1063
+ const response = await fetchStatus(new URL(`${ASSISTANT_AVATAR_STATUS_PATH}?pack=${installedId}`, origin).href, {
1064
+ cache: "no-store",
1065
+ credentials: "same-origin",
1066
+ signal: AbortSignal.timeout(250)
1067
+ });
1068
+ if (!response.ok) return void 0;
1069
+ const value = await response.json();
1070
+ if (value.version !== 1 || value.pack !== installedId || typeof value.generation !== "string" || !value.generation || value.generation.length > 80 || [
1071
+ value.cached,
1072
+ value.pending,
1073
+ value.total,
1074
+ value.downloads
1075
+ ].some((number) => !Number.isSafeInteger(number) || number < 0) || value.total > 256 || value.cached + value.pending > value.total) return void 0;
1076
+ return value;
1077
+ }
1078
+ const pack = ASSISTANT_AVATAR_RESOURCE_PACKS.find((entry) => model.origin === origin.origin && model.pathname === entry.basePath + entry.manifest);
1079
+ if (pack === void 0) return void 0;
1080
+ const response = await fetchStatus(new URL(assistantAvatarCacheStatusPath(pack), origin).href, {
1081
+ cache: "no-store",
1082
+ credentials: "same-origin",
1083
+ signal: AbortSignal.timeout(250)
1084
+ });
1085
+ if (!response.ok) return void 0;
1086
+ const value = await response.json();
1087
+ const numbers = [
1088
+ value.cached,
1089
+ value.pending,
1090
+ value.total,
1091
+ value.downloads
1092
+ ];
1093
+ const total = pack.files.filter((file) => file.preload !== false).length;
1094
+ if (value.version !== 1 || typeof value.generation !== "string" || value.generation.length === 0 || value.generation.length > 80 || value.pack !== pack.basePath || value.total !== total || numbers.some((number) => !Number.isSafeInteger(number) || number < 0) || value.cached + value.pending > total) return void 0;
1095
+ return value;
1096
+ } catch {
1097
+ return;
1098
+ }
1099
+ }
1100
+ const MAX_AGE = 2592e6;
1101
+ const DATABASE = "codingns-assistant-avatar-previews";
1102
+ const STORE = "previews";
1103
+ /** 排序映射,避免设置归一化复制对象或属性顺序变化造成无效缓存。 */
1104
+ function assistantAvatarPreviewKey(model, surface, rendererVersion, baseUrl = typeof document === "undefined" ? void 0 : document.baseURI) {
1105
+ const asset = resolveAssistantAvatarAsset(model, surface);
1106
+ const url = (source) => {
1107
+ try {
1108
+ return new URL(source, baseUrl).href;
1109
+ } catch {
1110
+ return source;
1111
+ }
1112
+ };
1113
+ const entries = (map) => Object.entries(map ?? {}).sort(([a], [b]) => a.localeCompare(b));
1114
+ return JSON.stringify([
1115
+ 1,
1116
+ model.id,
1117
+ surface,
1118
+ rendererVersion,
1119
+ asset.renderer,
1120
+ url(asset.source),
1121
+ asset.spriteVersion,
1122
+ asset.live2d?.scale ?? 1,
1123
+ asset.live2d?.position ?? [0, 0],
1124
+ entries(asset.motionGroups),
1125
+ entries(asset.stateSources).map(([state, source]) => [state, url(source)])
1126
+ ]);
1127
+ }
1128
+ function validAssistantAvatarPreview(value) {
1129
+ if (value === null || typeof value !== "object") return false;
1130
+ const preview = value;
1131
+ return preview.blob instanceof Blob && preview.blob.type === "image/png" && preview.blob.size > 0 && preview.blob.size <= 524288 && Number.isSafeInteger(preview.width) && Number.isSafeInteger(preview.height) && preview.width > 0 && preview.height > 0 && preview.width <= 512 && preview.height <= 512 && (preview.cropAreaPercentages === void 0 || validCropArea(preview.cropAreaPercentages));
1132
+ }
1133
+ function validCropArea(area) {
1134
+ return area !== null && typeof area === "object" && [
1135
+ area.x,
1136
+ area.y,
1137
+ area.width,
1138
+ area.height
1139
+ ].every(Number.isFinite) && area.x >= 0 && area.y >= 0 && area.width > 0 && area.height > 0 && area.x + area.width <= 100.001 && area.y + area.height <= 100.001;
1140
+ }
1141
+ /** 开库和事务都有短超时;存储不可用只丢失预览,不影响模型显示。 */
1142
+ var AssistantAvatarPreviewStore = class {
1143
+ factory;
1144
+ now;
1145
+ databaseName;
1146
+ limits;
1147
+ database;
1148
+ constructor(factory = () => typeof indexedDB === "undefined" ? void 0 : indexedDB, now = Date.now, databaseName = DATABASE, limits = {}) {
1149
+ this.factory = factory;
1150
+ this.now = now;
1151
+ this.databaseName = databaseName;
1152
+ this.limits = limits;
1153
+ }
1154
+ async read(key, signal) {
1155
+ return this.transaction("readonly", (store, result) => {
1156
+ const request = store.get(key);
1157
+ request.onsuccess = () => {
1158
+ const record = request.result;
1159
+ if (this.validRecord(record) && record.key === key) result({
1160
+ blob: record.blob,
1161
+ width: record.width,
1162
+ height: record.height,
1163
+ ...record.cropAreaPercentages === void 0 ? {} : { cropAreaPercentages: record.cropAreaPercentages }
1164
+ });
1165
+ };
1166
+ }, signal);
1167
+ }
1168
+ async write(key, modelId, surface, preview, signal) {
1169
+ if (!validAssistantAvatarPreview(preview)) return false;
1170
+ return await this.transaction("readwrite", (store, result) => {
1171
+ const request = store.getAll();
1172
+ request.onsuccess = () => {
1173
+ const records = [];
1174
+ for (const entry of request.result) if (!this.validRecord(entry) || entry.modelId === modelId && entry.surface === surface) store.delete(entry.key);
1175
+ else records.push(entry);
1176
+ records.sort((a, b) => b.savedAt - a.savedAt);
1177
+ for (const entry of records.slice((this.limits.maxEntries ?? 8) - 1)) store.delete(entry.key);
1178
+ store.put({
1179
+ key,
1180
+ modelId,
1181
+ surface,
1182
+ ...preview,
1183
+ savedAt: this.now()
1184
+ });
1185
+ result(true);
1186
+ };
1187
+ }, signal) ?? false;
1188
+ }
1189
+ async remove(key) {
1190
+ await this.transaction("readwrite", (store) => {
1191
+ store.delete(key);
1192
+ });
1193
+ }
1194
+ async removeModel(modelId) {
1195
+ await this.transaction("readwrite", (store) => {
1196
+ const request = store.getAll();
1197
+ request.onsuccess = () => {
1198
+ for (const entry of request.result) if (entry.modelId === modelId) store.delete(entry.key);
1199
+ };
1200
+ });
1201
+ }
1202
+ validRecord(value) {
1203
+ if (!validAssistantAvatarPreview(value)) return false;
1204
+ const entry = value;
1205
+ return typeof entry.key === "string" && typeof entry.modelId === "string" && (entry.surface === "floating" || entry.surface === "dialog") && Number.isFinite(entry.savedAt) && entry.savedAt <= this.now() && this.now() - entry.savedAt < (this.limits.maxAgeMs ?? MAX_AGE);
1206
+ }
1207
+ open() {
1208
+ if (this.database !== void 0) return this.database;
1209
+ const task = new Promise((resolve) => {
1210
+ let request;
1211
+ try {
1212
+ const factory = this.factory();
1213
+ if (factory === void 0) {
1214
+ resolve(void 0);
1215
+ return;
1216
+ }
1217
+ request = factory.open(this.databaseName, 1);
1218
+ } catch {
1219
+ resolve(void 0);
1220
+ return;
1221
+ }
1222
+ let settled = false;
1223
+ const finish = (database) => {
1224
+ if (settled) {
1225
+ database?.close();
1226
+ return;
1227
+ }
1228
+ settled = true;
1229
+ clearTimeout(timer);
1230
+ resolve(database);
1231
+ };
1232
+ const timer = setTimeout(() => finish(), 1e3);
1233
+ request.onupgradeneeded = () => {
1234
+ if (!request.result.objectStoreNames.contains(STORE)) request.result.createObjectStore(STORE, { keyPath: "key" });
1235
+ };
1236
+ request.onerror = () => finish();
1237
+ request.onblocked = () => finish();
1238
+ request.onsuccess = () => {
1239
+ request.result.onversionchange = () => {
1240
+ request.result.close();
1241
+ this.database = void 0;
1242
+ };
1243
+ finish(request.result);
1244
+ };
1245
+ });
1246
+ this.database = task;
1247
+ task.then((database) => {
1248
+ if (database === void 0 && this.database === task) this.database = void 0;
1249
+ });
1250
+ return task;
1251
+ }
1252
+ async transaction(mode, action, signal) {
1253
+ if (signal?.aborted) return void 0;
1254
+ const database = await this.open();
1255
+ if (database === void 0 || signal?.aborted) return void 0;
1256
+ return new Promise((resolve) => {
1257
+ let transaction;
1258
+ try {
1259
+ transaction = database.transaction(STORE, mode);
1260
+ } catch {
1261
+ this.database = void 0;
1262
+ resolve(void 0);
1263
+ return;
1264
+ }
1265
+ let value;
1266
+ const abort = () => {
1267
+ try {
1268
+ transaction.abort();
1269
+ } catch {}
1270
+ };
1271
+ const timer = setTimeout(abort, 1e3);
1272
+ const finish = (success) => {
1273
+ clearTimeout(timer);
1274
+ signal?.removeEventListener("abort", abort);
1275
+ resolve(success ? value : void 0);
1276
+ };
1277
+ transaction.oncomplete = () => finish(true);
1278
+ transaction.onabort = () => finish(false);
1279
+ transaction.onerror = () => finish(false);
1280
+ signal?.addEventListener("abort", abort, { once: true });
1281
+ try {
1282
+ action(transaction.objectStore(STORE), (next) => {
1283
+ value = next;
1284
+ });
1285
+ } catch {
1286
+ abort();
1287
+ finish(false);
1288
+ }
1289
+ });
1290
+ }
1291
+ };
1292
+ const assistantAvatarPreviewStore = new AssistantAvatarPreviewStore();
1293
+ //#endregion
1294
+ //#region src/client/avatar/preview-capture.ts
1295
+ /** 在同步绘制窗口拷贝到 2D 画布;PNG 编码异步执行,不保留原 GPU 缓冲。 */
1296
+ function copyCanvasFrame(source) {
1297
+ if (source.width <= 0 || source.height <= 0) return void 0;
1298
+ const canvas = document.createElement("canvas");
1299
+ const scale = Math.min(1, 512 / Math.max(source.width, source.height));
1300
+ canvas.width = Math.max(1, Math.round(source.width * scale));
1301
+ canvas.height = Math.max(1, Math.round(source.height * scale));
1302
+ const context = canvas.getContext("2d", { willReadFrequently: true });
1303
+ if (context === null) return void 0;
1304
+ context.drawImage(source, 0, 0, canvas.width, canvas.height);
1305
+ const pixels = context.getImageData(0, 0, canvas.width, canvas.height).data;
1306
+ for (let index = 3; index < pixels.length; index += 4) if (pixels[index] > 0) return canvas;
1307
+ }
1308
+ /** 只临时观察当前上下文的绘制,在同一帧微任务中捕获完整图像,完成/取消后还原。 */
1309
+ function captureAssistantAvatarPreview(source, signal) {
1310
+ if (signal?.aborted) return Promise.resolve(void 0);
1311
+ return new Promise((resolve) => {
1312
+ let settled = false;
1313
+ let queued = false;
1314
+ const restore = [];
1315
+ const finish = (preview) => {
1316
+ if (settled) return;
1317
+ settled = true;
1318
+ clearTimeout(timer);
1319
+ signal?.removeEventListener("abort", abort);
1320
+ for (const release of restore.splice(0).reverse()) release();
1321
+ resolve(preview);
1322
+ };
1323
+ const abort = () => finish();
1324
+ const timer = setTimeout(abort, 1e3);
1325
+ signal?.addEventListener("abort", abort, { once: true });
1326
+ const capture = () => {
1327
+ if (settled || signal?.aborted) return true;
1328
+ try {
1329
+ const canvas = copyCanvasFrame(source);
1330
+ if (canvas === void 0) return false;
1331
+ for (const release of restore.splice(0).reverse()) release();
1332
+ canvas.toBlob((blob) => {
1333
+ const preview = blob === null ? void 0 : {
1334
+ blob,
1335
+ width: canvas.width,
1336
+ height: canvas.height
1337
+ };
1338
+ finish(validAssistantAvatarPreview(preview) ? preview : void 0);
1339
+ }, "image/png");
1340
+ return true;
1341
+ } catch {
1342
+ finish();
1343
+ return true;
1344
+ }
1345
+ };
1346
+ try {
1347
+ const context = source.getContext("webgl2") ?? source.getContext("webgl");
1348
+ if (context === null) {
1349
+ if (!capture()) finish();
1350
+ return;
1351
+ }
1352
+ for (const name of ["drawElements", "drawArrays"]) {
1353
+ const original = context[name];
1354
+ const descriptor = Object.getOwnPropertyDescriptor(context, name);
1355
+ const observe = (...args) => {
1356
+ Reflect.apply(original, context, args);
1357
+ if (!queued && !settled) {
1358
+ queued = true;
1359
+ queueMicrotask(() => {
1360
+ queued = false;
1361
+ capture();
1362
+ });
1363
+ }
1364
+ };
1365
+ Object.defineProperty(context, name, {
1366
+ configurable: true,
1367
+ writable: true,
1368
+ value: observe
1369
+ });
1370
+ restore.push(() => {
1371
+ if (context[name] !== observe) return;
1372
+ try {
1373
+ if (descriptor === void 0) delete context[name];
1374
+ else Object.defineProperty(context, name, descriptor);
1375
+ } catch {}
1376
+ });
1377
+ }
1378
+ capture();
1379
+ } catch {
1380
+ finish();
1381
+ }
1382
+ });
1383
+ }
1384
+ /** 先去掉透明边距,再取上部角色轮廓;不修改原素材,也不识别具体角色 ID。 */
1385
+ function assistantAvatarPortraitCrop(pixels, width, height) {
1386
+ if (!Number.isSafeInteger(width) || !Number.isSafeInteger(height) || width < 1 || height < 1 || width > 512 || height > 512 || pixels.length !== width * height * 4) return void 0;
1387
+ let left = width, right = -1, top = height, bottom = -1;
1388
+ for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) {
1389
+ if (pixels[(y * width + x) * 4 + 3] < 24) continue;
1390
+ left = Math.min(left, x);
1391
+ right = Math.max(right, x);
1392
+ top = Math.min(top, y);
1393
+ bottom = Math.max(bottom, y);
1394
+ }
1395
+ if (right < left) return void 0;
1396
+ const upperBottom = Math.min(bottom, top + Math.ceil((bottom - top + 1) * .56));
1397
+ let upperLeft = width, upperRight = -1;
1398
+ for (let y = top; y <= upperBottom; y++) for (let x = left; x <= right; x++) {
1399
+ if (pixels[(y * width + x) * 4 + 3] < 24) continue;
1400
+ upperLeft = Math.min(upperLeft, x);
1401
+ upperRight = Math.max(upperRight, x);
1402
+ }
1403
+ const size = Math.max(1, Math.ceil(Math.max(upperRight - upperLeft + 1, upperBottom - top + 1) * 1.08));
1404
+ return {
1405
+ x: (upperLeft + upperRight + 1 - size) / 2,
1406
+ y: top - size * .04,
1407
+ size
1408
+ };
1409
+ }
1410
+ /** 图片和动作图集只加载一张现有素材;跨域不允许截图时安静回退。 */
1411
+ async function createImageAssistantAvatarPortrait(model, signal) {
1412
+ return portraitFromImage(assistantAvatarImageSource(model, "idle"), signal);
1413
+ }
1414
+ async function createBuiltinAssistantAvatarPortrait(model, signal) {
1415
+ return portraitFromImage(BUILTIN_ASSISTANT_AVATAR_SOURCES[model.id] ?? BUILTIN_ASSISTANT_AVATAR_SOURCES[BUILTIN_ASSISTANT_AVATAR.id], signal);
1416
+ }
1417
+ async function createSpriteAssistantAvatarPortrait(model, signal) {
1418
+ const image = await loadImage(model.source, signal);
1419
+ const rows = model.spriteVersion === 2 ? 11 : 9;
1420
+ if (image === void 0 || image.naturalWidth !== 1536 || image.naturalHeight !== 208 * rows) return void 0;
1421
+ return cropImage(image, signal, {
1422
+ x: 0,
1423
+ y: 0,
1424
+ width: 192,
1425
+ height: 208
1426
+ });
1427
+ }
1428
+ /** 微调使用完整静态角色或图集首帧,避免在已裁好的小头像上继续损失构图。 */
1429
+ async function createImageAssistantAvatarPortraitSource(model, signal) {
1430
+ const image = await loadImage(assistantAvatarImageSource(model, "idle"), signal);
1431
+ return image === void 0 ? void 0 : imageFrame(image, signal);
1432
+ }
1433
+ async function createBuiltinAssistantAvatarPortraitSource(model, signal) {
1434
+ return createImageAssistantAvatarPortraitSource({
1435
+ ...model,
1436
+ source: BUILTIN_ASSISTANT_AVATAR_SOURCES[model.id] ?? BUILTIN_ASSISTANT_AVATAR_SOURCES[BUILTIN_ASSISTANT_AVATAR.id]
1437
+ }, signal);
1438
+ }
1439
+ async function createSpriteAssistantAvatarPortraitSource(model, signal) {
1440
+ const image = await loadImage(model.source, signal);
1441
+ if (image === void 0 || image.naturalWidth !== 1536 || image.naturalHeight !== 208 * (model.spriteVersion === 2 ? 11 : 9)) return void 0;
1442
+ return imageFrame(image, signal, {
1443
+ x: 0,
1444
+ y: 0,
1445
+ width: 192,
1446
+ height: 208
1447
+ });
1448
+ }
1449
+ async function imageFrame(image, signal, frame = {
1450
+ x: 0,
1451
+ y: 0,
1452
+ width: image.naturalWidth,
1453
+ height: image.naturalHeight
1454
+ }) {
1455
+ if (signal.aborted || typeof document === "undefined") return void 0;
1456
+ try {
1457
+ const scale = Math.min(1, 512 / Math.max(frame.width, frame.height)), canvas = document.createElement("canvas");
1458
+ canvas.width = Math.max(1, Math.round(frame.width * scale));
1459
+ canvas.height = Math.max(1, Math.round(frame.height * scale));
1460
+ const context = canvas.getContext("2d");
1461
+ if (context === null) return void 0;
1462
+ context.drawImage(image, frame.x, frame.y, frame.width, frame.height, 0, 0, canvas.width, canvas.height);
1463
+ return await encodePortrait(canvas, signal);
1464
+ } catch {
1465
+ return;
1466
+ }
1467
+ }
1468
+ /** 裁剪组件仅负责交互及坐标;像素输出保留透明背景并统一成 128px。 */
1469
+ async function cropAssistantAvatarPortrait(source, area, signal) {
1470
+ if (signal.aborted || ![
1471
+ area.x,
1472
+ area.y,
1473
+ area.width,
1474
+ area.height
1475
+ ].every(Number.isFinite) || area.width <= 0 || area.height <= 0 || Math.abs(area.width - area.height) > 1 || area.x < 0 || area.y < 0 || area.x + area.width > source.width + 1 || area.y + area.height > source.height + 1) return void 0;
1476
+ const url = URL.createObjectURL(source.blob);
1477
+ try {
1478
+ const image = await loadImage(url, signal);
1479
+ if (image === void 0 || typeof document === "undefined") return void 0;
1480
+ const canvas = document.createElement("canvas");
1481
+ canvas.width = 128;
1482
+ canvas.height = 128;
1483
+ const context = canvas.getContext("2d");
1484
+ if (context === null) return void 0;
1485
+ context.imageSmoothingQuality = "high";
1486
+ context.drawImage(image, area.x, area.y, area.width, area.height, 0, 0, 128, 128);
1487
+ return await encodePortrait(canvas, signal);
1488
+ } catch {
1489
+ return;
1490
+ } finally {
1491
+ URL.revokeObjectURL(url);
1492
+ }
1493
+ }
1494
+ /** 初始化裁剪框仍落在角色上部,组件随后负责拖动与缩放。 */
1495
+ async function initialAssistantAvatarPortraitArea(source, signal) {
1496
+ if (signal.aborted) return void 0;
1497
+ const url = URL.createObjectURL(source.blob);
1498
+ try {
1499
+ const image = await loadImage(url, signal);
1500
+ if (image === void 0 || typeof document === "undefined") return void 0;
1501
+ const canvas = document.createElement("canvas");
1502
+ canvas.width = source.width;
1503
+ canvas.height = source.height;
1504
+ const context = canvas.getContext("2d", { willReadFrequently: true });
1505
+ if (context === null) return void 0;
1506
+ context.drawImage(image, 0, 0, canvas.width, canvas.height);
1507
+ const crop = assistantAvatarPortraitCrop(context.getImageData(0, 0, canvas.width, canvas.height).data, canvas.width, canvas.height);
1508
+ if (crop === void 0) return void 0;
1509
+ const size = Math.min(crop.size, canvas.width, canvas.height);
1510
+ return {
1511
+ x: Math.max(0, Math.min(canvas.width - size, crop.x)) / canvas.width * 100,
1512
+ y: Math.max(0, Math.min(canvas.height - size, crop.y)) / canvas.height * 100,
1513
+ width: size / canvas.width * 100,
1514
+ height: size / canvas.height * 100
1515
+ };
1516
+ } catch {
1517
+ return;
1518
+ } finally {
1519
+ URL.revokeObjectURL(url);
1520
+ }
1521
+ }
1522
+ async function createAssistantAvatarPortraitFromPreview(preview, signal) {
1523
+ if (signal.aborted) return void 0;
1524
+ const url = URL.createObjectURL(preview.blob);
1525
+ try {
1526
+ return await portraitFromImage(url, signal);
1527
+ } finally {
1528
+ URL.revokeObjectURL(url);
1529
+ }
1530
+ }
1531
+ async function portraitFromImage(source, signal) {
1532
+ const image = await loadImage(source, signal);
1533
+ return image === void 0 ? void 0 : cropImage(image, signal);
1534
+ }
1535
+ function loadImage(source, signal) {
1536
+ if (signal.aborted || typeof Image === "undefined") return Promise.resolve(void 0);
1537
+ return new Promise((resolve) => {
1538
+ const image = new Image();
1539
+ let finished = false;
1540
+ const finish = (success = false) => {
1541
+ if (finished) return;
1542
+ finished = true;
1543
+ clearTimeout(timer);
1544
+ signal.removeEventListener("abort", abort);
1545
+ image.onload = null;
1546
+ image.onerror = null;
1547
+ if (!success) image.src = "";
1548
+ resolve(success ? image : void 0);
1549
+ };
1550
+ const abort = () => finish();
1551
+ const timer = setTimeout(abort, 3e4);
1552
+ signal.addEventListener("abort", abort, { once: true });
1553
+ image.crossOrigin = "anonymous";
1554
+ image.onload = () => finish(image.naturalWidth > 0 && image.naturalHeight > 0);
1555
+ image.onerror = abort;
1556
+ image.src = source;
1557
+ });
1558
+ }
1559
+ async function cropImage(image, signal, frame = {
1560
+ x: 0,
1561
+ y: 0,
1562
+ width: image.naturalWidth,
1563
+ height: image.naturalHeight
1564
+ }) {
1565
+ if (signal.aborted || typeof document === "undefined") return void 0;
1566
+ try {
1567
+ const scale = Math.min(1, 512 / Math.max(frame.width, frame.height));
1568
+ const sample = document.createElement("canvas");
1569
+ sample.width = Math.max(1, Math.round(frame.width * scale));
1570
+ sample.height = Math.max(1, Math.round(frame.height * scale));
1571
+ const context = sample.getContext("2d", { willReadFrequently: true });
1572
+ if (context === null) return void 0;
1573
+ context.drawImage(image, frame.x, frame.y, frame.width, frame.height, 0, 0, sample.width, sample.height);
1574
+ const crop = assistantAvatarPortraitCrop(context.getImageData(0, 0, sample.width, sample.height).data, sample.width, sample.height);
1575
+ if (crop === void 0) return void 0;
1576
+ const output = document.createElement("canvas");
1577
+ output.width = 128;
1578
+ output.height = 128;
1579
+ const target = output.getContext("2d");
1580
+ if (target === null) return void 0;
1581
+ target.imageSmoothingEnabled = true;
1582
+ target.imageSmoothingQuality = "high";
1583
+ const zoom = output.width / crop.size;
1584
+ target.setTransform(zoom, 0, 0, zoom, -crop.x * zoom, -crop.y * zoom);
1585
+ target.drawImage(sample, 0, 0);
1586
+ return await encodePortrait(output, signal);
1587
+ } catch {
1588
+ return;
1589
+ }
1590
+ }
1591
+ function encodePortrait(canvas, signal) {
1592
+ return new Promise((resolve) => {
1593
+ let settled = false;
1594
+ const finish = (blob) => {
1595
+ if (settled) return;
1596
+ settled = true;
1597
+ clearTimeout(timer);
1598
+ signal.removeEventListener("abort", abort);
1599
+ resolve(!signal.aborted && blob !== void 0 && blob !== null && blob.size > 0 && blob.size <= 524288 ? {
1600
+ blob,
1601
+ width: canvas.width,
1602
+ height: canvas.height
1603
+ } : void 0);
1604
+ };
1605
+ const abort = () => finish();
1606
+ const timer = setTimeout(abort, 1e3);
1607
+ signal.addEventListener("abort", abort, { once: true });
1608
+ if (signal.aborted) {
1609
+ finish();
1610
+ return;
1611
+ }
1612
+ canvas.toBlob((blob) => finish(blob), "image/png");
1613
+ });
1614
+ }
1615
+ //#endregion
1616
+ //#region src/client/avatar/live2d.ts
1617
+ /** 成功共享引擎;失败更换模块 URL,避免浏览器缓存失败的动态导入而无法恢复。 */
1618
+ var AssistantLive2dModuleLoader = class {
1619
+ importModule;
1620
+ fetchStatus;
1621
+ module;
1622
+ attempt = 0;
1623
+ retryAt = 0;
1624
+ revision = assistantAvatarRuntimeRevision();
1625
+ constructor(importModule = (url) => import(
1626
+ /* @vite-ignore */
1627
+ url
1628
+ ), fetchStatus = (...args) => fetch(...args)) {
1629
+ this.importModule = importModule;
1630
+ this.fetchStatus = fetchStatus;
1631
+ }
1632
+ load(location) {
1633
+ if (this.revision !== assistantAvatarRuntimeRevision() || this.retryAt > 0 && Date.now() >= this.retryAt) {
1634
+ this.revision = assistantAvatarRuntimeRevision();
1635
+ this.module = void 0;
1636
+ this.retryAt = 0;
1637
+ }
1638
+ if (this.module !== void 0) return this.module;
1639
+ const url = new URL(ASSISTANT_AVATAR_RUNTIME_PATH, location);
1640
+ url.searchParams.set("v", "2.1.1");
1641
+ const attempt = this.attempt++;
1642
+ if (attempt > 0) url.searchParams.set("retry", String(attempt));
1643
+ const request = withLoadDeadline(this.importModule(url.href), 45e3).then((module) => {
1644
+ if (typeof module.init !== "function") throw new Error("avatar_runtime_invalid");
1645
+ return module;
1646
+ }).catch(async (error) => {
1647
+ if (this.module === request) this.retryAt = Date.now() + 3e4;
1648
+ let status;
1649
+ try {
1650
+ status = (await this.fetchStatus(url, {
1651
+ credentials: "same-origin",
1652
+ signal: AbortSignal.timeout(5e3)
1653
+ })).status;
1654
+ } catch {}
1655
+ if (status === 503) throw new Error("avatar_runtime_unavailable");
1656
+ if (status !== void 0 && status !== 200) throw new Error(`avatar_runtime_http_${status}`);
1657
+ throw error;
1658
+ });
1659
+ this.module = request;
1660
+ return request;
1661
+ }
1662
+ };
1663
+ const runtimeModuleLoader = new AssistantLive2dModuleLoader();
1664
+ /** 固定同源独立 ESM,只在用户选择 Live2D 时加载;不进入主 Client bundle。 */
1665
+ function loadLive2dModule() {
1666
+ return runtimeModuleLoader.load(window.location.href);
1667
+ }
1668
+ /** l2d 某些下载失败只记录日志而不拒绝 Promise,必须限制永久等待。 */
1669
+ async function withLoadDeadline(task, timeoutMs, observeActivity) {
1670
+ let timer;
1671
+ try {
1672
+ return await Promise.race([task, new Promise((_resolve, reject) => {
1673
+ const refresh = () => {
1674
+ clearTimeout(timer);
1675
+ timer = setTimeout(() => reject(/* @__PURE__ */ new Error("avatar_load_timeout")), timeoutMs);
1676
+ };
1677
+ observeActivity?.(refresh);
1678
+ refresh();
1679
+ })]);
1680
+ } finally {
1681
+ clearTimeout(timer);
1682
+ observeActivity?.(void 0);
1683
+ }
1684
+ }
1685
+ /** 参考上游 load/setState/resize/destroy;旧加载完成不能复活已销毁实例。 */
1686
+ var AssistantLive2dController = class {
1687
+ runtime;
1688
+ motionGroups;
1689
+ options;
1690
+ timeoutMs;
1691
+ disposed = false;
1692
+ state = "idle";
1693
+ loaded = false;
1694
+ loadConfirmed = false;
1695
+ cancelLoad;
1696
+ refreshDeadline;
1697
+ report;
1698
+ constructor(runtime, motionGroups, options, report, timeoutMs = 45e3) {
1699
+ this.runtime = runtime;
1700
+ this.motionGroups = motionGroups;
1701
+ this.options = options;
1702
+ this.timeoutMs = timeoutMs;
1703
+ this.report = report;
1704
+ runtime.on?.("loadstart", (total) => {
1705
+ if (this.disposed) return;
1706
+ this.refreshDeadline?.();
1707
+ this.report?.({
1708
+ phase: "resources",
1709
+ loaded: 0,
1710
+ total
1711
+ });
1712
+ });
1713
+ runtime.on?.("loadprogress", (loaded, total) => {
1714
+ if (this.disposed) return;
1715
+ this.refreshDeadline?.();
1716
+ this.report?.({
1717
+ phase: total > 0 && loaded >= total ? "rendering" : "resources",
1718
+ loaded,
1719
+ total
1720
+ });
1721
+ });
1722
+ runtime.on?.("loaded", () => {
1723
+ if (!this.disposed) this.loadConfirmed = true;
1724
+ });
1725
+ }
1726
+ async load(source) {
1727
+ if (this.disposed) return false;
1728
+ this.loadConfirmed = false;
1729
+ try {
1730
+ const operation = this.runtime.load({
1731
+ path: source,
1732
+ scale: this.options?.scale ?? 1,
1733
+ volume: 0,
1734
+ logLevel: "warn",
1735
+ ...this.options?.position === void 0 ? {} : { position: [...this.options.position] }
1736
+ }).then(() => {
1737
+ if (this.disposed) {
1738
+ this.runtime.destroy();
1739
+ return false;
1740
+ }
1741
+ if (this.runtime.on !== void 0 && !this.loadConfirmed) throw new Error("avatar_model_not_loaded");
1742
+ return true;
1743
+ }, (error) => {
1744
+ if (this.disposed) {
1745
+ this.runtime.destroy();
1746
+ return false;
1747
+ }
1748
+ throw error;
1749
+ });
1750
+ const cancelled = new Promise((resolve) => {
1751
+ this.cancelLoad = () => resolve(false);
1752
+ });
1753
+ if (!await withLoadDeadline(Promise.race([operation, cancelled]), this.timeoutMs, (refresh) => {
1754
+ this.refreshDeadline = refresh;
1755
+ })) return false;
1756
+ this.loaded = true;
1757
+ this.setState(this.state);
1758
+ return true;
1759
+ } catch (error) {
1760
+ this.dispose();
1761
+ throw error;
1762
+ } finally {
1763
+ this.cancelLoad = void 0;
1764
+ }
1765
+ }
1766
+ setState(state) {
1767
+ if (this.disposed) return;
1768
+ this.state = state;
1769
+ if (!this.loaded) return;
1770
+ const groups = Object.keys(this.runtime.getMotions());
1771
+ const desired = state === "speaking" ? /talk|speak/iu : state === "thinking" ? /think|work/iu : state === "error" ? /fail|error|sad/iu : state === "listening" || state === "waiting" ? /listen|wait/iu : /idle/iu;
1772
+ const configured = this.motionGroups?.[state];
1773
+ const group = configured !== void 0 && groups.includes(configured) ? configured : groups.find((name) => desired.test(name)) ?? groups.find((name) => /idle/iu.test(name));
1774
+ if (group !== void 0) this.runtime.playMotion(group, void 0, 2);
1775
+ }
1776
+ resize() {
1777
+ if (!this.disposed && this.loaded) this.runtime.resize();
1778
+ }
1779
+ dispose() {
1780
+ if (this.disposed) return;
1781
+ this.disposed = true;
1782
+ this.report = void 0;
1783
+ this.cancelLoad?.();
1784
+ this.runtime.destroy();
1785
+ }
1786
+ };
1787
+ /** 没有已绘制预览时只生成一次头像;捕获后立即销毁临时 WebGL 实例。 */
1788
+ async function createLive2dAssistantAvatarPortrait(model, signal) {
1789
+ const preview = await createLive2dAssistantAvatarPortraitSource(model, signal);
1790
+ return preview === void 0 ? void 0 : createAssistantAvatarPortraitFromPreview(preview, signal);
1791
+ }
1792
+ /** 编辑器复用同一静态帧来源,临时实例的销毁仍由适配器统一完成。 */
1793
+ async function createLive2dAssistantAvatarPortraitSource(model, signal) {
1794
+ if (signal.aborted || typeof document === "undefined") return void 0;
1795
+ const module = await loadLive2dModule();
1796
+ if (signal.aborted) return void 0;
1797
+ const canvas = document.createElement("canvas");
1798
+ canvas.width = 512;
1799
+ canvas.height = 555;
1800
+ canvas.style.cssText = "position:fixed;left:-10000px;top:0;width:256px;height:277.5px;pointer-events:none;opacity:0";
1801
+ canvas.setAttribute("aria-hidden", "true");
1802
+ document.body.appendChild(canvas);
1803
+ let controller;
1804
+ const abort = () => controller?.dispose();
1805
+ try {
1806
+ const runtime = module.init(canvas);
1807
+ if (runtime === null) return void 0;
1808
+ controller = new AssistantLive2dController(runtime, model.motionGroups, model.live2d);
1809
+ signal.addEventListener("abort", abort, { once: true });
1810
+ if (signal.aborted || !await controller.load(model.source) || signal.aborted) return void 0;
1811
+ return await captureAssistantAvatarPreview(runtime.getCanvas?.() ?? canvas, signal);
1812
+ } finally {
1813
+ signal.removeEventListener("abort", abort);
1814
+ controller?.dispose();
1815
+ canvas.remove();
1816
+ }
1817
+ }
1818
+ function Live2dAssistantAvatar({ model, state, size, onError, onLoadProgress, diagnostics = false, onPreview }) {
1819
+ const configKey = JSON.stringify([model.motionGroups, model.live2d]);
1820
+ const config = (0, react.useMemo)(() => ({
1821
+ motionGroups: model.motionGroups,
1822
+ options: model.live2d
1823
+ }), [configKey]);
1824
+ const canvas = (0, react.useRef)(null);
1825
+ const controller = (0, react.useRef)(void 0);
1826
+ const currentState = (0, react.useRef)(state);
1827
+ currentState.current = state;
1828
+ const [visible, setVisible] = (0, react.useState)(() => typeof document === "undefined" || !document.hidden);
1829
+ (0, react.useEffect)(() => {
1830
+ const refresh = () => setVisible(!document.hidden);
1831
+ document.addEventListener("visibilitychange", refresh);
1832
+ return () => document.removeEventListener("visibilitychange", refresh);
1833
+ }, []);
1834
+ (0, react.useEffect)(() => {
1835
+ const target = canvas.current;
1836
+ if (target === null || !visible) return void 0;
1837
+ let disposed = false;
1838
+ let instance;
1839
+ const previewController = new AbortController();
1840
+ const started = diagnostics ? performance.now() : 0;
1841
+ let progress = { phase: "engine" };
1842
+ const report = (next) => {
1843
+ if (disposed) return;
1844
+ if (!diagnostics) {
1845
+ onLoadProgress?.(next);
1846
+ return;
1847
+ }
1848
+ const elapsedMs = performance.now() - started;
1849
+ progress = {
1850
+ ...progress,
1851
+ ...next,
1852
+ elapsedMs,
1853
+ ...next.phase === "rendering" && progress.resourcesMs === void 0 ? { resourcesMs: elapsedMs } : {}
1854
+ };
1855
+ onLoadProgress?.(progress);
1856
+ };
1857
+ report(progress);
1858
+ const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(() => instance?.resize());
1859
+ observer?.observe(target);
1860
+ Promise.all([loadLive2dModule(), diagnostics ? readAssistantAvatarCacheStatus(model.source, window.location.href) : void 0]).then(async ([module, before]) => {
1861
+ if (disposed) return;
1862
+ report({
1863
+ phase: "resources",
1864
+ ...before === void 0 ? {} : { cacheBefore: before }
1865
+ });
1866
+ const runtime = module.init(target);
1867
+ if (runtime === null) throw new Error("avatar_webgl_unavailable");
1868
+ instance = new AssistantLive2dController(runtime, config.motionGroups, config.options, report);
1869
+ controller.current = instance;
1870
+ instance.setState(currentState.current);
1871
+ if (!await instance.load(model.source) || disposed) return;
1872
+ if (onPreview !== void 0) {
1873
+ const preview = await captureAssistantAvatarPreview(runtime.getCanvas?.() ?? target, previewController.signal);
1874
+ if (disposed) return;
1875
+ if (preview !== void 0) onPreview(preview);
1876
+ }
1877
+ report({ phase: "ready" });
1878
+ if (!diagnostics) return;
1879
+ const finished = progress;
1880
+ const after = await readAssistantAvatarCacheStatus(model.source, window.location.href);
1881
+ if (!disposed && after !== void 0) {
1882
+ progress = {
1883
+ ...finished,
1884
+ cacheAfter: after
1885
+ };
1886
+ onLoadProgress?.(progress);
1887
+ }
1888
+ }).catch((error) => {
1889
+ if (!disposed) onError(error instanceof Error ? error.message : String(error));
1890
+ });
1891
+ return () => {
1892
+ disposed = true;
1893
+ previewController.abort();
1894
+ observer?.disconnect();
1895
+ instance?.dispose();
1896
+ controller.current = void 0;
1897
+ };
1898
+ }, [
1899
+ model.source,
1900
+ config,
1901
+ visible,
1902
+ onError,
1903
+ onLoadProgress,
1904
+ diagnostics,
1905
+ onPreview
1906
+ ]);
1907
+ (0, react.useEffect)(() => {
1908
+ controller.current?.setState(state);
1909
+ }, [state]);
1910
+ return (0, react.createElement)("canvas", {
1911
+ ref: canvas,
1912
+ width: Math.round(size * 2),
1913
+ height: Math.round(size * 208 / 192 * 2),
1914
+ "aria-hidden": true,
1915
+ style: {
1916
+ width: size,
1917
+ height: "auto",
1918
+ aspectRatio: "192 / 208",
1919
+ maxWidth: "100%",
1920
+ display: "block"
1921
+ }
1922
+ });
1923
+ }
1924
+ //#endregion
1925
+ //#region src/shared/assistant-avatar-registration.ts
1926
+ /** 所有形象适配器与渲染器共用稳定注册快照;注销只清理自己的一代。 */
1927
+ var AssistantAvatarRegistration = class {
1928
+ entries = /* @__PURE__ */ new Map();
1929
+ reserved;
1930
+ listeners = /* @__PURE__ */ new Set();
1931
+ snapshot;
1932
+ constructor(builtins = []) {
1933
+ for (const entry of builtins) this.entries.set(entry.id, entry);
1934
+ this.reserved = new Set(this.entries.keys());
1935
+ this.snapshot = Object.freeze([...this.entries.values()]);
1936
+ }
1937
+ subscribe = (listener) => {
1938
+ this.listeners.add(listener);
1939
+ return () => {
1940
+ this.listeners.delete(listener);
1941
+ };
1942
+ };
1943
+ getSnapshot = () => this.snapshot;
1944
+ get(id) {
1945
+ return this.entries.get(id);
1946
+ }
1947
+ register(entry) {
1948
+ if (!/^[A-Za-z][A-Za-z0-9_-]{0,79}$/u.test(entry.id) || this.reserved.has(entry.id)) throw new TypeError("形象扩展 ID 无效或占用内置名称");
1949
+ if (this.entries.has(entry.id)) throw new TypeError(`形象扩展已注册: ${entry.id}`);
1950
+ this.entries.set(entry.id, entry);
1951
+ this.emit();
1952
+ return () => {
1953
+ if (this.entries.get(entry.id) !== entry) return;
1954
+ this.entries.delete(entry.id);
1955
+ this.emit();
1956
+ };
1957
+ }
1958
+ emit() {
1959
+ this.snapshot = Object.freeze([...this.entries.values()]);
1960
+ for (const listener of this.listeners) listener();
1961
+ }
1962
+ };
1963
+ //#endregion
1964
+ //#region src/client/avatar/registry.ts
1965
+ /** 与语音适配器一样按服务实例隔离,页面卸载不会污染另一份 Client。 */
1966
+ var AssistantAvatarRegistry = class extends AssistantAvatarRegistration {
1967
+ constructor() {
1968
+ super([
1969
+ {
1970
+ id: "builtin",
1971
+ component: BuiltinAssistantAvatar,
1972
+ reportsLoading: true,
1973
+ portraitVersion: "1",
1974
+ createPortrait: createBuiltinAssistantAvatarPortrait,
1975
+ createPortraitSource: createBuiltinAssistantAvatarPortraitSource
1976
+ },
1977
+ {
1978
+ id: "image",
1979
+ labelKey: "avatar.type.image",
1980
+ component: ImageAssistantAvatar,
1981
+ reportsLoading: true,
1982
+ portraitVersion: "1",
1983
+ createPortrait: createImageAssistantAvatarPortrait,
1984
+ createPortraitSource: createImageAssistantAvatarPortraitSource
1985
+ },
1986
+ {
1987
+ id: "spritesheet",
1988
+ labelKey: "avatar.type.spritesheet",
1989
+ component: SpriteSheetAssistantAvatar,
1990
+ reportsLoading: true,
1991
+ portraitVersion: "1",
1992
+ createPortrait: createSpriteAssistantAvatarPortrait,
1993
+ createPortraitSource: createSpriteAssistantAvatarPortraitSource
1994
+ },
1995
+ {
1996
+ id: "live2d",
1997
+ labelKey: "avatar.type.live2d",
1998
+ component: Live2dAssistantAvatar,
1999
+ reportsLoading: true,
2000
+ initialLoadPhase: "engine",
2001
+ reportsCache: true,
2002
+ previewVersion: "1",
2003
+ portraitVersion: "1",
2004
+ createPortrait: createLive2dAssistantAvatarPortrait,
2005
+ createPortraitSource: createLive2dAssistantAvatarPortraitSource
2006
+ }
2007
+ ]);
2008
+ }
2009
+ };
2010
+ const registries = /* @__PURE__ */ new WeakMap();
2011
+ /** 配置草稿借用正式渲染器注册表,保留第三方扩展。 */
2012
+ function bindAssistantAvatarRegistry(services, registry) {
2013
+ registries.set(services, registry);
2014
+ }
2015
+ function getAssistantAvatarRegistry(services) {
2016
+ let registry = registries.get(services);
2017
+ if (registry === void 0) {
2018
+ registry = new AssistantAvatarRegistry();
2019
+ registries.set(services, registry);
2020
+ }
2021
+ return registry;
2022
+ }
2023
+ /** 扩展模块应将返回的注销函数登记到自己的 context.resources。 */
2024
+ function registerAssistantAvatarRenderer(services, renderer) {
2025
+ return getAssistantAvatarRegistry(services).register(renderer);
2026
+ }
2027
+ //#endregion
2028
+ //#region src/client/avatar/loading.ts
2029
+ const loadingStyle = `
2030
+ @keyframes codingns-avatar-orbit { to { transform: rotate(360deg); } }
2031
+ @keyframes codingns-avatar-breathe { 50% { transform: scale(1.08); opacity: .65; } }
2032
+ @keyframes codingns-avatar-shimmer { from { transform: translateX(-130%); } to { transform: translateX(330%); } }
2033
+ [data-codingns-avatar-loading] .codingns-avatar-halo { animation: codingns-avatar-breathe 2.6s ease-in-out infinite; }
2034
+ [data-codingns-avatar-loading] .codingns-avatar-orbit { animation: codingns-avatar-orbit 3s linear infinite; }
2035
+ [data-codingns-avatar-loading] .codingns-avatar-indeterminate { animation: codingns-avatar-shimmer 1.6s ease-in-out infinite; }
2036
+ @media (prefers-reduced-motion: reduce) {
2037
+ [data-codingns-avatar-loading] .codingns-avatar-halo,
2038
+ [data-codingns-avatar-loading] .codingns-avatar-orbit,
2039
+ [data-codingns-avatar-loading] .codingns-avatar-indeterminate { animation: none; }
2040
+ }`;
2041
+ /** 百分比只根据已经完成的文件计数生成;未知总数保留循环动画。 */
2042
+ function assistantAvatarLoadPercent(progress) {
2043
+ if (progress.total === void 0 || progress.loaded === void 0 || !Number.isFinite(progress.total) || !Number.isFinite(progress.loaded) || progress.total <= 0) return void 0;
2044
+ return Math.round(Math.min(1, Math.max(0, progress.loaded / progress.total)) * 100);
2045
+ }
2046
+ /** 计数只在同一缓存代际内可相减,Host 重建后不能把相同数字解释成下载为零。 */
2047
+ function newHostDownloads(progress) {
2048
+ const before = progress.cacheBefore;
2049
+ const after = progress.cacheAfter;
2050
+ if (before === void 0 || after === void 0 || after.generation !== before.generation || after.downloads < before.downloads) return void 0;
2051
+ return after.downloads - before.downloads;
2052
+ }
2053
+ function cacheText(progress, t) {
2054
+ const before = progress.cacheBefore;
2055
+ if (before === void 0) return t("avatar.cacheUnknown");
2056
+ const initial = t("avatar.cacheBefore", {
2057
+ cached: before.cached,
2058
+ total: before.total
2059
+ });
2060
+ const count = newHostDownloads(progress);
2061
+ return count === void 0 ? initial : `${initial} · ${t("avatar.cacheDownloads", { count })}`;
2062
+ }
2063
+ /** 透明光晕、轨道光点与细进度条,直接复用插槽尺寸和宿主主题。 */
2064
+ function AssistantAvatarLoading({ progress, size, t, showCache = false, diagnostics = false, preview = false, animationOnly = false }) {
2065
+ showCache = !animationOnly && diagnostics && showCache;
2066
+ if (progress.phase === "ready" && (!diagnostics || animationOnly)) return null;
2067
+ const percent = assistantAvatarLoadPercent(progress);
2068
+ const compact = size < 120;
2069
+ const ready = progress.phase === "ready";
2070
+ const elapsed = progress.elapsedMs === void 0 ? "" : t("avatar.loadElapsed", { seconds: (progress.elapsedMs / 1e3).toFixed(2) });
2071
+ const shortElapsed = progress.elapsedMs === void 0 ? "" : t("avatar.loadElapsedCompact", { seconds: (progress.elapsedMs / 1e3).toFixed(2) });
2072
+ const details = [
2073
+ showCache ? cacheText(progress, t) : "",
2074
+ elapsed,
2075
+ progress.resourcesMs === void 0 ? "" : t("avatar.loadResourcesElapsed", { seconds: (progress.resourcesMs / 1e3).toFixed(2) })
2076
+ ].filter(Boolean).join(" · ");
2077
+ const newDownloads = newHostDownloads(progress);
2078
+ if (ready) return (0, react.createElement)("span", {
2079
+ "data-codingns-avatar-load-summary": true,
2080
+ title: details,
2081
+ style: {
2082
+ position: "absolute",
2083
+ bottom: 0,
2084
+ maxWidth: "100%",
2085
+ fontSize: 10,
2086
+ lineHeight: 1.4,
2087
+ color: require_theme.dshThemeColor.labelSecondary,
2088
+ textAlign: "center",
2089
+ background: require_theme.dshThemeColor.cardBackground,
2090
+ borderRadius: 8,
2091
+ padding: "2px 6px"
2092
+ }
2093
+ }, showCache ? (0, react.createElement)("span", { style: { display: "block" } }, progress.cacheBefore === void 0 ? t("avatar.cacheUnknown") : t(compact ? "avatar.cacheTiny" : "avatar.cacheShort", {
2094
+ cached: progress.cacheBefore.cached,
2095
+ total: progress.cacheBefore.total
2096
+ })) : null, (0, react.createElement)("span", { style: { display: "block" } }, [showCache && newDownloads !== void 0 ? t(compact ? "avatar.cacheDownloadsTiny" : "avatar.cacheDownloadsCompact", { count: newDownloads }) : "", compact && showCache && newDownloads !== void 0 ? "" : shortElapsed].filter(Boolean).join(" · ")));
2097
+ const phaseText = progress.phase === "engine" ? t("avatar.loadEngine") : progress.phase === "rendering" ? t("avatar.loadRendering") : t("avatar.loading");
2098
+ const orbSize = preview ? 14 : Math.min(80, size * (compact ? .3 : .46));
2099
+ const cache = progress.cacheBefore;
2100
+ const cacheLabel = compact ? cache === void 0 ? t("avatar.cacheUnknownCompact") : t("avatar.cacheTiny", {
2101
+ cached: cache.cached,
2102
+ total: cache.total
2103
+ }) : cacheText(progress, t);
2104
+ return (0, react.createElement)("div", {
2105
+ "data-codingns-avatar-loading": progress.phase,
2106
+ role: "status",
2107
+ "aria-label": phaseText,
2108
+ ...showCache ? { title: cacheText(progress, t) } : {},
2109
+ style: {
2110
+ position: "absolute",
2111
+ inset: preview ? "auto 0 0" : 0,
2112
+ display: "flex",
2113
+ flexDirection: "column",
2114
+ justifyContent: "center",
2115
+ alignItems: "center",
2116
+ gap: compact || preview ? 4 : 10,
2117
+ ...preview ? {
2118
+ padding: "5px 4px",
2119
+ borderRadius: 10,
2120
+ background: require_theme.dshThemeColor.cardBackground
2121
+ } : {},
2122
+ pointerEvents: "none",
2123
+ color: require_theme.dshThemeColor.labelSecondary,
2124
+ textAlign: "center"
2125
+ }
2126
+ }, (0, react.createElement)("style", null, loadingStyle), (0, react.createElement)("div", { style: {
2127
+ display: "flex",
2128
+ flexDirection: preview ? "row" : "column",
2129
+ alignItems: "center",
2130
+ gap: compact || preview ? 4 : 10
2131
+ } }, (0, react.createElement)("div", {
2132
+ "aria-hidden": true,
2133
+ style: {
2134
+ position: "relative",
2135
+ flexShrink: 0,
2136
+ width: orbSize,
2137
+ height: orbSize,
2138
+ color: require_theme.dshThemeColor.accent
2139
+ }
2140
+ }, (0, react.createElement)("div", {
2141
+ className: "codingns-avatar-halo",
2142
+ style: {
2143
+ position: "absolute",
2144
+ inset: "-14%",
2145
+ borderRadius: "50%",
2146
+ background: "radial-gradient(circle, rgba(112, 148, 255, .24), rgba(132, 204, 247, .10) 55%, transparent 72%)"
2147
+ }
2148
+ }), (0, react.createElement)("div", {
2149
+ className: "codingns-avatar-orbit",
2150
+ style: {
2151
+ position: "absolute",
2152
+ inset: 0,
2153
+ borderRadius: "50%",
2154
+ border: "1px solid rgba(112, 148, 255, .20)"
2155
+ }
2156
+ }, (0, react.createElement)("span", { style: {
2157
+ position: "absolute",
2158
+ top: -3,
2159
+ left: "50%",
2160
+ width: 6,
2161
+ height: 6,
2162
+ borderRadius: "50%",
2163
+ background: "currentColor",
2164
+ boxShadow: "0 0 12px rgba(112, 148, 255, .5)"
2165
+ } })), (0, react.createElement)("svg", {
2166
+ viewBox: "0 0 64 64",
2167
+ style: {
2168
+ position: "absolute",
2169
+ inset: "20%",
2170
+ width: "60%",
2171
+ height: "60%"
2172
+ },
2173
+ fill: "none"
2174
+ }, (0, react.createElement)("path", {
2175
+ d: "M32 9C35 23 41 29 55 32C41 35 35 41 32 55C29 41 23 35 9 32C23 29 29 23 32 9Z",
2176
+ fill: "currentColor",
2177
+ opacity: .85
2178
+ }), (0, react.createElement)("circle", {
2179
+ cx: 51,
2180
+ cy: 12,
2181
+ r: 3,
2182
+ fill: "currentColor",
2183
+ opacity: .4
2184
+ }))), animationOnly ? null : (0, react.createElement)("span", { style: {
2185
+ fontSize: compact || preview ? 10 : size < 160 ? 11 : 13,
2186
+ fontWeight: 500
2187
+ } }, compact || preview ? t("avatar.loadingCompact") : phaseText)), animationOnly ? null : (0, react.createElement)("div", {
2188
+ role: "progressbar",
2189
+ "aria-label": t("avatar.loadProgress"),
2190
+ "aria-valuemin": 0,
2191
+ "aria-valuemax": 100,
2192
+ ...percent === void 0 ? {} : { "aria-valuenow": percent },
2193
+ style: {
2194
+ width: Math.min(130, size * .74),
2195
+ height: 3,
2196
+ flexShrink: 0,
2197
+ borderRadius: 3,
2198
+ overflow: "hidden",
2199
+ background: "rgba(112, 148, 255, .15)"
2200
+ }
2201
+ }, (0, react.createElement)("div", {
2202
+ className: percent === void 0 ? "codingns-avatar-indeterminate" : void 0,
2203
+ style: {
2204
+ width: percent === void 0 ? "30%" : `${percent}%`,
2205
+ height: "100%",
2206
+ borderRadius: 3,
2207
+ background: "linear-gradient(90deg, #81c9ed, #7791f7)",
2208
+ transition: "width .2s ease"
2209
+ }
2210
+ })), animationOnly ? null : (0, react.createElement)("span", { style: {
2211
+ fontSize: 10,
2212
+ lineHeight: 1.5,
2213
+ padding: "0 6px",
2214
+ maxWidth: "100%"
2215
+ } }, percent === void 0 ? null : `${progress.loaded}/${progress.total} · ${percent}%`, showCache ? (0, react.createElement)("span", { style: { display: "block" } }, cacheLabel) : null));
2216
+ }
2217
+ //#endregion
2218
+ //#region src/client/avatar/preview.ts
2219
+ /** 插槽拥有读写与 URL 生命周期;渲染器仅提交图像,不接触持久化存储。 */
2220
+ function useAssistantAvatarPreview(model, surface, version) {
2221
+ const key = version === void 0 ? void 0 : assistantAvatarPreviewKey(model, surface, version);
2222
+ const [preview, setPreview] = (0, react.useState)();
2223
+ const session = (0, react.useRef)(void 0);
2224
+ (0, react.useEffect)(() => {
2225
+ setPreview(void 0);
2226
+ if (key === void 0) return void 0;
2227
+ const active = {
2228
+ key,
2229
+ controller: new AbortController(),
2230
+ published: false,
2231
+ url: void 0
2232
+ };
2233
+ session.current = active;
2234
+ assistantAvatarPreviewStore.read(key, active.controller.signal).then((snapshot) => {
2235
+ if (snapshot === void 0 || active.controller.signal.aborted || active.published) return;
2236
+ active.url = URL.createObjectURL(snapshot.blob);
2237
+ setPreview({
2238
+ key,
2239
+ url: active.url,
2240
+ width: snapshot.width,
2241
+ height: snapshot.height
2242
+ });
2243
+ }).catch(() => void 0);
2244
+ return () => {
2245
+ active.controller.abort();
2246
+ if (active.url !== void 0) URL.revokeObjectURL(active.url);
2247
+ if (session.current === active) session.current = void 0;
2248
+ };
2249
+ }, [key]);
2250
+ const onPreview = (0, react.useCallback)((snapshot) => {
2251
+ const active = session.current;
2252
+ if (key === void 0 || active?.key !== key || active.controller.signal.aborted || !validAssistantAvatarPreview(snapshot)) return;
2253
+ active.published = true;
2254
+ if (active.url === void 0) try {
2255
+ active.url = URL.createObjectURL(snapshot.blob);
2256
+ setPreview({
2257
+ key,
2258
+ url: active.url,
2259
+ width: snapshot.width,
2260
+ height: snapshot.height
2261
+ });
2262
+ } catch {}
2263
+ assistantAvatarPreviewStore.write(key, model.id, surface, snapshot, active.controller.signal).catch(() => void 0);
2264
+ }, [
2265
+ key,
2266
+ model.id,
2267
+ surface
2268
+ ]);
2269
+ const discard = (0, react.useCallback)((stop = false) => {
2270
+ const active = session.current;
2271
+ if (active === void 0 || active.key !== key) return;
2272
+ active.published = true;
2273
+ if (stop) active.controller.abort();
2274
+ if (active.url !== void 0) URL.revokeObjectURL(active.url);
2275
+ active.url = void 0;
2276
+ setPreview(void 0);
2277
+ if (key !== void 0) assistantAvatarPreviewStore.remove(key).catch(() => void 0);
2278
+ }, [key]);
2279
+ return {
2280
+ preview: preview?.key === key ? preview : void 0,
2281
+ onPreview: version === void 0 ? void 0 : onPreview,
2282
+ discard
2283
+ };
2284
+ }
2285
+ //#endregion
2286
+ //#region src/client/avatar/portrait-service.ts
2287
+ const assistantAvatarPortraitStore = new AssistantAvatarPreviewStore(void 0, void 0, "codingns-assistant-avatar-portraits");
2288
+ /** 用户确认的头像属于本地配置,不按临时预览的 30 天规则失效。 */
2289
+ const assistantAvatarPortraitOverrideStore = new AssistantAvatarPreviewStore(void 0, void 0, "codingns-assistant-avatar-portrait-overrides", {
2290
+ maxAgeMs: Infinity,
2291
+ maxEntries: 21
2292
+ });
2293
+ /** 头像始终采用对话形象的待机构图,语音状态、名称和显示尺寸不参与缓存键。 */
2294
+ function assistantAvatarPortraitKey(model, renderer) {
2295
+ const asset = resolveAssistantAvatarAsset(model, "dialog");
2296
+ return JSON.stringify([
2297
+ "portrait",
2298
+ 1,
2299
+ renderer?.previewVersion ?? "",
2300
+ assistantAvatarPreviewKey(model, "dialog", renderer?.portraitVersion ?? ""),
2301
+ asset.renderer === "builtin" ? BUILTIN_ASSISTANT_AVATAR_SOURCES[model.id] : ""
2302
+ ]);
2303
+ }
2304
+ function validPortrait(value) {
2305
+ return validAssistantAvatarPreview(value) && value.width === value.height && value.width <= 256;
2306
+ }
2307
+ function sameAsset(a, b) {
2308
+ return assistantAvatarPreviewKey(a, "dialog", "") === assistantAvatarPreviewKey({
2309
+ ...b,
2310
+ surfaces: {}
2311
+ }, "dialog", "");
2312
+ }
2313
+ /** 每个 Client 共用一个任务和临时 URL;多条消息不会创建多个图片或 WebGL 实例。 */
2314
+ var AssistantAvatarPortraitService = class {
2315
+ store;
2316
+ previews;
2317
+ crop;
2318
+ createUrl;
2319
+ revokeUrl;
2320
+ overrides;
2321
+ entries = /* @__PURE__ */ new Map();
2322
+ clock = 0;
2323
+ constructor(store = assistantAvatarPortraitStore, previews = assistantAvatarPreviewStore, crop = createAssistantAvatarPortraitFromPreview, createUrl = (blob) => URL.createObjectURL(blob), revokeUrl = (url) => URL.revokeObjectURL(url), overrides = assistantAvatarPortraitOverrideStore) {
2324
+ this.store = store;
2325
+ this.previews = previews;
2326
+ this.crop = crop;
2327
+ this.createUrl = createUrl;
2328
+ this.revokeUrl = revokeUrl;
2329
+ this.overrides = overrides;
2330
+ }
2331
+ /** 编辑器仅在用户打开时读取完整静态帧,普通头像订阅不增加素材加载。 */
2332
+ async getSource(model, renderer, signal) {
2333
+ const asset = resolveAssistantAvatarAsset(model, "dialog");
2334
+ const manual = await this.overrides.read(assistantAvatarPortraitKey(model, renderer), signal).catch(() => void 0);
2335
+ const withArea = (frame) => ({
2336
+ ...frame,
2337
+ ...manual?.cropAreaPercentages === void 0 ? {} : { cropAreaPercentages: manual.cropAreaPercentages }
2338
+ });
2339
+ if (renderer?.previewVersion !== void 0) for (const surface of ["dialog", "floating"]) {
2340
+ if (!sameAsset(asset, resolveAssistantAvatarAsset(model, surface))) continue;
2341
+ const frame = await this.previews.read(assistantAvatarPreviewKey(model, surface, renderer.previewVersion), signal).catch(() => void 0);
2342
+ if (signal.aborted) return void 0;
2343
+ if (validAssistantAvatarPreview(frame)) return withArea(frame);
2344
+ }
2345
+ if (signal.aborted) return void 0;
2346
+ const frame = await renderer?.createPortraitSource?.(asset, signal);
2347
+ if (signal.aborted) return void 0;
2348
+ if (validAssistantAvatarPreview(frame)) return withArea(frame);
2349
+ const fallback = manual ?? await this.store.read(assistantAvatarPortraitKey(model, renderer), signal);
2350
+ return fallback === void 0 ? void 0 : {
2351
+ blob: fallback.blob,
2352
+ width: fallback.width,
2353
+ height: fallback.height
2354
+ };
2355
+ }
2356
+ /** 先确保持久化成功才发布,存储失败和取消都保留当前头像。 */
2357
+ async setPortrait(model, renderer, preview, signal) {
2358
+ if (!validPortrait(preview)) throw new TypeError("avatar_portrait_invalid");
2359
+ signal.throwIfAborted();
2360
+ const entry = this.entry(model, renderer), controller = new AbortController();
2361
+ entry.controller?.abort();
2362
+ entry.controller = controller;
2363
+ const abort = () => controller.abort();
2364
+ signal.addEventListener("abort", abort, { once: true });
2365
+ try {
2366
+ const saved = await this.overrides.write(entry.snapshot.key, model.id, "dialog", preview, controller.signal);
2367
+ signal.throwIfAborted();
2368
+ if (!saved || entry.controller !== controller || controller.signal.aborted) throw new Error("avatar_portrait_write_failed");
2369
+ this.publish(entry, preview);
2370
+ entry.completed = true;
2371
+ } finally {
2372
+ signal.removeEventListener("abort", abort);
2373
+ if (entry.controller === controller) entry.controller = void 0;
2374
+ }
2375
+ }
2376
+ getSnapshot(model, renderer) {
2377
+ return this.entry(model, renderer).snapshot;
2378
+ }
2379
+ subscribe(model, renderer, listener) {
2380
+ const entry = this.entry(model, renderer);
2381
+ entry.listeners.add(listener);
2382
+ return () => {
2383
+ entry.listeners.delete(listener);
2384
+ this.trim();
2385
+ };
2386
+ }
2387
+ retain(model, renderer) {
2388
+ const entry = this.entry(model, renderer);
2389
+ entry.refs++;
2390
+ entry.touched = ++this.clock;
2391
+ if (!entry.completed && entry.controller === void 0) this.start(entry);
2392
+ return () => {
2393
+ entry.refs = Math.max(0, entry.refs - 1);
2394
+ if (entry.refs === 0) {
2395
+ entry.controller?.abort();
2396
+ entry.controller = void 0;
2397
+ }
2398
+ this.trim();
2399
+ };
2400
+ }
2401
+ /** 插槽已绘制的帧优先于临时渲染任务;取消旧任务并隔离迟到结果。 */
2402
+ offerPreview(model, surface, renderer, preview) {
2403
+ if (!validAssistantAvatarPreview(preview)) return;
2404
+ const asset = resolveAssistantAvatarAsset(model, surface);
2405
+ for (const entry of this.entries.values()) {
2406
+ if (entry.refs === 0 || !entry.overrideChecked || entry.snapshot.url !== void 0 || entry.renderer !== renderer || !sameAsset(entry.asset, asset)) continue;
2407
+ entry.controller?.abort();
2408
+ const controller = new AbortController();
2409
+ entry.controller = controller;
2410
+ entry.completed = false;
2411
+ this.crop(preview, controller.signal).then((portrait) => this.commit(entry, controller, portrait)).catch(() => void 0).finally(() => this.finish(entry, controller));
2412
+ }
2413
+ }
2414
+ removeModel(id) {
2415
+ for (const entry of this.entries.values()) {
2416
+ if (entry.model.id !== id) continue;
2417
+ entry.controller?.abort();
2418
+ entry.controller = void 0;
2419
+ entry.completed = false;
2420
+ if (entry.snapshot.url !== void 0) this.revokeUrl(entry.snapshot.url);
2421
+ entry.snapshot = { key: entry.snapshot.key };
2422
+ for (const listener of entry.listeners) listener();
2423
+ if (entry.refs === 0 && entry.listeners.size === 0) this.entries.delete(entry.snapshot.key);
2424
+ }
2425
+ this.store.removeModel(id).catch(() => void 0);
2426
+ this.overrides.removeModel(id).catch(() => void 0);
2427
+ }
2428
+ /** 配置窗口销毁时回收仅供本地预览使用的资源地址。 */
2429
+ dispose() {
2430
+ for (const entry of this.entries.values()) {
2431
+ entry.controller?.abort();
2432
+ if (entry.snapshot.url !== void 0) this.revokeUrl(entry.snapshot.url);
2433
+ }
2434
+ this.entries.clear();
2435
+ }
2436
+ entry(model, renderer) {
2437
+ const key = assistantAvatarPortraitKey(model, renderer);
2438
+ let entry = this.entries.get(key);
2439
+ if (entry === void 0) {
2440
+ entry = {
2441
+ model,
2442
+ asset: resolveAssistantAvatarAsset(model, "dialog"),
2443
+ renderer,
2444
+ snapshot: { key },
2445
+ listeners: /* @__PURE__ */ new Set(),
2446
+ refs: 0,
2447
+ touched: ++this.clock,
2448
+ completed: false,
2449
+ overrideChecked: false,
2450
+ controller: void 0
2451
+ };
2452
+ this.entries.set(key, entry);
2453
+ } else if (entry.renderer !== renderer) {
2454
+ entry.controller?.abort();
2455
+ entry.controller = void 0;
2456
+ entry.renderer = renderer;
2457
+ entry.completed = entry.snapshot.url !== void 0;
2458
+ }
2459
+ return entry;
2460
+ }
2461
+ start(entry) {
2462
+ const controller = new AbortController();
2463
+ entry.controller = controller;
2464
+ this.load(entry, controller.signal).then((preview) => this.commit(entry, controller, preview)).catch(() => void 0).finally(() => this.finish(entry, controller));
2465
+ }
2466
+ async load(entry, signal) {
2467
+ const manual = await this.overrides.read(entry.snapshot.key, signal).catch(() => void 0);
2468
+ if (signal.aborted || validPortrait(manual)) return signal.aborted ? void 0 : manual;
2469
+ entry.overrideChecked = true;
2470
+ const cached = await this.store.read(entry.snapshot.key, signal).catch(() => void 0);
2471
+ if (signal.aborted || validPortrait(cached)) return signal.aborted ? void 0 : cached;
2472
+ const version = entry.renderer?.previewVersion;
2473
+ if (version !== void 0) for (const surface of ["dialog", "floating"]) {
2474
+ if (!sameAsset(entry.asset, resolveAssistantAvatarAsset(entry.model, surface))) continue;
2475
+ const frame = await this.previews.read(assistantAvatarPreviewKey(entry.model, surface, version), signal).catch(() => void 0);
2476
+ if (signal.aborted) return void 0;
2477
+ if (frame === void 0) continue;
2478
+ const portrait = await this.crop(frame, signal);
2479
+ if (portrait !== void 0 || signal.aborted) return portrait;
2480
+ }
2481
+ return signal.aborted ? void 0 : entry.renderer?.createPortrait?.(entry.asset, signal);
2482
+ }
2483
+ commit(entry, controller, preview) {
2484
+ if (entry.controller !== controller || controller.signal.aborted || !validPortrait(preview)) return;
2485
+ this.publish(entry, preview);
2486
+ this.store.write(entry.snapshot.key, entry.model.id, "dialog", preview, controller.signal).catch(() => void 0);
2487
+ }
2488
+ publish(entry, preview) {
2489
+ const previous = entry.snapshot.url, url = this.createUrl(preview.blob);
2490
+ entry.snapshot = {
2491
+ key: entry.snapshot.key,
2492
+ url
2493
+ };
2494
+ entry.touched = ++this.clock;
2495
+ for (const listener of entry.listeners) listener();
2496
+ if (previous !== void 0) this.revokeUrl(previous);
2497
+ }
2498
+ finish(entry, controller) {
2499
+ if (entry.controller !== controller) return;
2500
+ entry.controller = void 0;
2501
+ entry.completed = !controller.signal.aborted;
2502
+ this.trim();
2503
+ }
2504
+ trim() {
2505
+ const idle = [...this.entries.values()].filter((entry) => entry.refs === 0 && entry.listeners.size === 0).sort((a, b) => b.touched - a.touched);
2506
+ for (const entry of idle.slice(8)) {
2507
+ entry.controller?.abort();
2508
+ if (entry.snapshot.url !== void 0) this.revokeUrl(entry.snapshot.url);
2509
+ this.entries.delete(entry.snapshot.key);
2510
+ }
2511
+ }
2512
+ };
2513
+ const services = /* @__PURE__ */ new WeakMap();
2514
+ function bindAssistantAvatarPortraitService(client, service) {
2515
+ services.set(client, service);
2516
+ }
2517
+ function getAssistantAvatarPortraitService(client) {
2518
+ let service = services.get(client);
2519
+ if (service === void 0) {
2520
+ service = new AssistantAvatarPortraitService();
2521
+ services.set(client, service);
2522
+ }
2523
+ return service;
2524
+ }
2525
+ //#endregion
2526
+ //#region src/client/avatar/slot.ts
2527
+ /** 两个容器只调用这个插槽;渲染错误始终被限制在形象区域。 */
2528
+ function AssistantAvatarSlot(props) {
2529
+ const model = resolveAssistantAvatarAsset(props.model, props.surface);
2530
+ const registry = getAssistantAvatarRegistry(props.services);
2531
+ const readRenderer = (0, react.useCallback)(() => registry.get(model.renderer), [registry, model.renderer]);
2532
+ const renderer = (0, react.useSyncExternalStore)(registry.subscribe, readRenderer, readRenderer);
2533
+ const runtimeRevision = (0, react.useSyncExternalStore)(subscribeAssistantAvatarRuntime, assistantAvatarRuntimeRevision, assistantAvatarRuntimeRevision);
2534
+ const generation = (0, react.useRef)({
2535
+ renderer,
2536
+ value: 0
2537
+ });
2538
+ if (generation.current.renderer !== renderer) generation.current = {
2539
+ renderer,
2540
+ value: generation.current.value + 1
2541
+ };
2542
+ return (0, react.createElement)(AvatarErrorBoundary, {
2543
+ key: `${JSON.stringify(props.model)}:${generation.current.value}:${model.renderer === "live2d" ? runtimeRevision : 0}`,
2544
+ fallback: (0, react.createElement)(AvatarFallback, props)
2545
+ }, (0, react.createElement)(AvatarContent, {
2546
+ ...props,
2547
+ model,
2548
+ renderer
2549
+ }));
2550
+ }
2551
+ function AvatarContent({ services, model, state, surface, size, renderer, showDiagnostics, transient }) {
2552
+ const [error, setError] = (0, react.useState)("");
2553
+ const [progress, setProgress] = (0, react.useState)(() => renderer?.reportsLoading === true ? { phase: renderer.initialLoadPhase ?? "resources" } : void 0);
2554
+ const { preview, onPreview, discard } = useAssistantAvatarPreview(model, surface, transient ? void 0 : renderer?.previewVersion);
2555
+ const previewKey = assistantAvatarPreviewKey(model, surface, renderer?.previewVersion ?? "");
2556
+ const publishPreview = (0, react.useCallback)((snapshot) => {
2557
+ onPreview?.(snapshot);
2558
+ if (!transient && renderer !== void 0) getAssistantAvatarPortraitService(services).offerPreview(model, surface, renderer, snapshot);
2559
+ }, [
2560
+ services,
2561
+ previewKey,
2562
+ surface,
2563
+ renderer,
2564
+ onPreview,
2565
+ transient
2566
+ ]);
2567
+ const onError = (0, react.useCallback)((message) => {
2568
+ discard(true);
2569
+ setError(message);
2570
+ }, [discard]);
2571
+ const onLoadProgress = (0, react.useCallback)((next) => setProgress(next), []);
2572
+ const t = require_transport.useCodingNsTranslator(services.locale);
2573
+ const diagnostics = services.stage0 === true && showDiagnostics !== false;
2574
+ const props = {
2575
+ model,
2576
+ state,
2577
+ surface,
2578
+ size,
2579
+ onError,
2580
+ onLoadProgress,
2581
+ diagnostics,
2582
+ ...onPreview === void 0 ? {} : { onPreview: publishPreview }
2583
+ };
2584
+ const component = renderer?.component;
2585
+ const showCache = diagnostics && (renderer?.reportsCache === true || progress?.cacheBefore !== void 0 || progress?.cacheAfter !== void 0);
2586
+ const content = component === void 0 ? null : (0, react.createElement)(component, props);
2587
+ if (error !== "" || content === null) return (0, react.createElement)(AvatarFallback, {
2588
+ services,
2589
+ model,
2590
+ state,
2591
+ surface,
2592
+ size,
2593
+ ...transient === void 0 ? {} : { transient },
2594
+ ...error === "" ? {} : { error }
2595
+ });
2596
+ return (0, react.createElement)("div", {
2597
+ "data-codingns-avatar-slot": surface,
2598
+ "data-codingns-avatar-renderer": model.renderer,
2599
+ "data-codingns-avatar-state": state,
2600
+ "aria-label": t("avatar.character", { name: model.name }),
2601
+ "aria-busy": progress !== void 0 && progress.phase !== "ready",
2602
+ style: {
2603
+ position: "relative",
2604
+ display: "grid",
2605
+ justifyItems: "center",
2606
+ maxWidth: "100%",
2607
+ minWidth: 0
2608
+ }
2609
+ }, (0, react.createElement)("div", { style: {
2610
+ opacity: progress !== void 0 && progress.phase !== "ready" ? 0 : 1,
2611
+ transition: "opacity .25s ease",
2612
+ maxWidth: "100%"
2613
+ } }, content), preview === void 0 ? null : (0, react.createElement)("img", {
2614
+ key: preview.url,
2615
+ src: preview.url,
2616
+ alt: "",
2617
+ "aria-hidden": true,
2618
+ draggable: false,
2619
+ "data-codingns-avatar-preview": true,
2620
+ onError: () => discard(),
2621
+ style: {
2622
+ position: "absolute",
2623
+ inset: 0,
2624
+ width: "100%",
2625
+ height: "100%",
2626
+ objectFit: "contain",
2627
+ pointerEvents: "none",
2628
+ opacity: progress !== void 0 && progress.phase !== "ready" ? 1 : 0,
2629
+ transition: "opacity .25s ease"
2630
+ }
2631
+ }), progress === void 0 || progress.phase === "ready" && (!diagnostics || progress.elapsedMs === void 0 && !showCache) ? null : (0, react.createElement)(AssistantAvatarLoading, {
2632
+ progress,
2633
+ size,
2634
+ t,
2635
+ showCache,
2636
+ diagnostics,
2637
+ preview: preview !== void 0,
2638
+ animationOnly: transient === true
2639
+ }));
2640
+ }
2641
+ const loadErrorKeys = {
2642
+ avatar_runtime_unavailable: "avatar.live2dEngineUnavailable",
2643
+ avatar_runtime_invalid: "avatar.live2dRuntimeInvalid",
2644
+ avatar_webgl_unavailable: "avatar.live2dWebglUnavailable"
2645
+ };
2646
+ function AvatarFallback(props) {
2647
+ const t = require_transport.useCodingNsTranslator(props.services.locale);
2648
+ const key = props.error === void 0 ? void 0 : loadErrorKeys[props.error];
2649
+ const detail = props.error === void 0 ? null : (0, react.createElement)("small", {
2650
+ "data-codingns-avatar-load-error": true,
2651
+ style: {
2652
+ display: "block",
2653
+ marginTop: 8,
2654
+ overflowWrap: "anywhere"
2655
+ }
2656
+ }, t("avatar.loadErrorReason", { reason: key === void 0 ? props.error : t(key) }));
2657
+ if (props.transient) return (0, react.createElement)("div", {
2658
+ role: "alert",
2659
+ "data-codingns-avatar-preview-failed": true,
2660
+ style: require_theme.dshSettingsHelpStyle
2661
+ }, t(props.model.renderer === "live2d" ? "avatar.temporaryLive2dLoadFailed" : "avatar.temporaryLoadFailed"), detail);
2662
+ return (0, react.createElement)("div", {
2663
+ "data-codingns-avatar-fallback": true,
2664
+ style: {
2665
+ display: "grid",
2666
+ justifyItems: "center",
2667
+ maxWidth: "100%"
2668
+ }
2669
+ }, (0, react.createElement)(BuiltinAssistantAvatar, {
2670
+ ...props,
2671
+ model: BUILTIN_ASSISTANT_AVATAR,
2672
+ onError: () => void 0
2673
+ }), (0, react.createElement)("span", {
2674
+ role: "status",
2675
+ style: {
2676
+ ...require_theme.dshSettingsHelpStyle,
2677
+ color: require_theme.dshThemeColor.labelSecondary,
2678
+ textAlign: "center",
2679
+ maxWidth: props.size
2680
+ }
2681
+ }, t("avatar.loadFailed")), detail);
2682
+ }
2683
+ var AvatarErrorBoundary = class extends react.Component {
2684
+ state = { failed: false };
2685
+ static getDerivedStateFromError() {
2686
+ return { failed: true };
2687
+ }
2688
+ render() {
2689
+ return this.state.failed ? this.props.fallback : this.props.children;
2690
+ }
2691
+ };
2692
+ //#endregion
2693
+ //#region src/client/avatar/portrait.ts
2694
+ /** 只订阅当前形象,切换时立即回到对应占位,不显示上一角色的迟到头像。 */
2695
+ function useAssistantAvatarPortrait(services, model) {
2696
+ const registry = getAssistantAvatarRegistry(services);
2697
+ const rendererId = resolveAssistantAvatarAsset(model, "dialog").renderer;
2698
+ const readRenderer = (0, react.useCallback)(() => registry.get(rendererId), [registry, rendererId]);
2699
+ const renderer = (0, react.useSyncExternalStore)(registry.subscribe, readRenderer, readRenderer);
2700
+ const service = getAssistantAvatarPortraitService(services);
2701
+ const key = assistantAvatarPortraitKey(model, renderer);
2702
+ const read = (0, react.useCallback)(() => service.getSnapshot(model, renderer), [
2703
+ service,
2704
+ key,
2705
+ renderer
2706
+ ]);
2707
+ const subscribe = (0, react.useCallback)((listener) => service.subscribe(model, renderer, listener), [
2708
+ service,
2709
+ key,
2710
+ renderer
2711
+ ]);
2712
+ const portrait = (0, react.useSyncExternalStore)(subscribe, read, read);
2713
+ (0, react.useEffect)(() => service.retain(model, renderer), [
2714
+ service,
2715
+ key,
2716
+ renderer
2717
+ ]);
2718
+ return {
2719
+ url: portrait.url ?? BUILTIN_ASSISTANT_AVATAR_SOURCES[model.id] ?? BUILTIN_ASSISTANT_AVATAR_SOURCES[BUILTIN_ASSISTANT_AVATAR.id],
2720
+ generated: portrait.url !== void 0
2721
+ };
2722
+ }
2723
+ /** 消息头像仅显示静态 PNG;名字和按钮本身提供无障碍说明。 */
2724
+ function AssistantAvatarPortrait({ services, model, size = 28 }) {
2725
+ const portrait = useAssistantAvatarPortrait(services, model);
2726
+ return (0, react.createElement)("span", {
2727
+ "data-codingns-avatar-portrait": model.id,
2728
+ "aria-hidden": true,
2729
+ style: {
2730
+ display: "inline-flex",
2731
+ width: size,
2732
+ height: size,
2733
+ flex: `0 0 ${size}px`,
2734
+ borderRadius: "50%",
2735
+ overflow: "hidden"
2736
+ }
2737
+ }, (0, react.createElement)("img", {
2738
+ src: portrait.url,
2739
+ alt: "",
2740
+ draggable: false,
2741
+ style: assistantAvatarPortraitImageStyle(portrait.generated),
2742
+ onError: (event) => fallbackPortraitImage(event.currentTarget)
2743
+ }));
2744
+ }
2745
+ function assistantAvatarPortraitImageStyle(generated) {
2746
+ return {
2747
+ display: "block",
2748
+ width: "100%",
2749
+ height: "100%",
2750
+ objectFit: "contain",
2751
+ pointerEvents: "none",
2752
+ ...generated ? {} : { transform: "translateY(26%) scale(1.7)" }
2753
+ };
2754
+ }
2755
+ /** 更新填充而不重建原生按钮,保留点击、焦点和无障碍属性。 */
2756
+ function fillAssistantAvatarButton(button, portrait) {
2757
+ let image = button.querySelector("img[data-codingns-avatar-button-portrait]");
2758
+ if (image === null) {
2759
+ image = button.ownerDocument.createElement("img");
2760
+ image.alt = "";
2761
+ image.draggable = false;
2762
+ image.setAttribute("aria-hidden", "true");
2763
+ image.setAttribute("data-codingns-avatar-button-portrait", "true");
2764
+ button.replaceChildren(image);
2765
+ }
2766
+ image.style.transform = "";
2767
+ Object.assign(image.style, assistantAvatarPortraitImageStyle(portrait.generated));
2768
+ image.onerror = () => fallbackPortraitImage(image);
2769
+ if (image.getAttribute("src") !== portrait.url) image.src = portrait.url;
2770
+ button.style.overflow = "hidden";
2771
+ }
2772
+ function fallbackPortraitImage(image) {
2773
+ const source = BUILTIN_ASSISTANT_AVATAR_SOURCES[BUILTIN_ASSISTANT_AVATAR.id];
2774
+ if (image.getAttribute("src") === source) return;
2775
+ Object.assign(image.style, assistantAvatarPortraitImageStyle(false));
2776
+ image.src = source;
2777
+ }
2778
+ //#endregion
2779
+ //#region src/client/avatar/manager.ts
2780
+ var AssistantAvatarAdapterRegistry = class extends AssistantAvatarRegistration {
2781
+ constructor() {
2782
+ super(BUILTIN_ASSISTANT_AVATAR_ADAPTERS);
2783
+ }
2784
+ parse(manifest, manifestUrl, adapterId = "auto") {
2785
+ const adapter = adapterId === "auto" ? this.getSnapshot().find((item) => item.matches(manifest)) : this.get(adapterId);
2786
+ if (adapter === void 0 || !adapter.matches(manifest)) throw new TypeError("无法识别形象包,或所选适配器未注册");
2787
+ const model = adapter.parse(manifest, { manifestUrl });
2788
+ const result = {
2789
+ ...model,
2790
+ package: {
2791
+ ...model.package,
2792
+ adapterId: adapter.id,
2793
+ manifestUrl
2794
+ }
2795
+ };
2796
+ validateAssistantAvatarModel(result);
2797
+ return copyAssistantAvatarModel(result);
2798
+ }
2799
+ };
2800
+ /** 唯一写入入口:包导入与手工素材最终都变成同一清单,不拥有语音或会话。 */
2801
+ var AssistantAvatarManager = class {
2802
+ settings;
2803
+ adapters;
2804
+ fetchManifest;
2805
+ baseUrl;
2806
+ rpc;
2807
+ removePortrait;
2808
+ writes = Promise.resolve();
2809
+ appearance;
2810
+ catalog = [];
2811
+ constructor(settings, adapters = new AssistantAvatarAdapterRegistry(), fetchManifest = (...args) => fetch(...args), baseUrl = typeof location === "undefined" ? void 0 : location.href, rpc, removePortrait = (id) => {
2812
+ assistantAvatarPortraitStore.removeModel(id).catch(() => void 0);
2813
+ assistantAvatarPortraitOverrideStore.removeModel(id).catch(() => void 0);
2814
+ }) {
2815
+ this.settings = settings;
2816
+ this.adapters = adapters;
2817
+ this.fetchManifest = fetchManifest;
2818
+ this.baseUrl = baseUrl;
2819
+ this.rpc = rpc;
2820
+ this.removePortrait = removePortrait;
2821
+ }
2822
+ /** React 和扩展都可以订阅这一份稳定快照,语音设置变化不会刷新形象列表。 */
2823
+ getSnapshot = () => {
2824
+ const next = normalizeAssistantAppearance(this.settings.getSnapshot().value?.assistant.appearance);
2825
+ if (this.appearance === void 0 || !sameSettingsValue(this.appearance, next)) {
2826
+ this.appearance = next;
2827
+ this.catalog = Object.freeze(listAssistantAvatars(next));
2828
+ }
2829
+ return this.appearance;
2830
+ };
2831
+ subscribe = (listener) => {
2832
+ let previous = this.getSnapshot();
2833
+ return this.settings.subscribe(() => {
2834
+ const next = this.getSnapshot();
2835
+ if (previous === next) return;
2836
+ previous = next;
2837
+ listener();
2838
+ });
2839
+ };
2840
+ getAppearance() {
2841
+ return this.getSnapshot();
2842
+ }
2843
+ list() {
2844
+ this.getSnapshot();
2845
+ return this.catalog;
2846
+ }
2847
+ getSelected() {
2848
+ return selectedAssistantAvatar(this.getAppearance());
2849
+ }
2850
+ select(id) {
2851
+ return this.write((current) => {
2852
+ this.requireModel(current, id);
2853
+ return {
2854
+ ...current,
2855
+ selectedId: id
2856
+ };
2857
+ });
2858
+ }
2859
+ async add(model) {
2860
+ validateAssistantAvatarModel(model);
2861
+ const copy = copyAssistantAvatarModel(model);
2862
+ return this.insert(copy);
2863
+ }
2864
+ async update(model) {
2865
+ this.requireCustom(model.id);
2866
+ validateAssistantAvatarModel(model);
2867
+ const copy = copyAssistantAvatarModel(model);
2868
+ return this.write((current) => {
2869
+ this.requireModel(current, copy.id);
2870
+ return {
2871
+ ...current,
2872
+ models: current.models.some((item) => item.id === copy.id) ? current.models.map((item) => item.id === copy.id ? copy : item) : [...current.models, copy]
2873
+ };
2874
+ });
2875
+ }
2876
+ async remove(id) {
2877
+ this.requireCustom(id);
2878
+ const installation = this.list().find((model) => model.id === id)?.package?.installationId;
2879
+ await this.write((current) => {
2880
+ this.requireModel(current, id);
2881
+ return {
2882
+ ...current,
2883
+ selectedId: current.selectedId === id ? BUILTIN_ASSISTANT_AVATAR.id : current.selectedId,
2884
+ models: current.models.filter((item) => item.id !== id)
2885
+ };
2886
+ });
2887
+ assistantAvatarPreviewStore.removeModel(id).catch(() => void 0);
2888
+ this.removePortrait(id);
2889
+ if (installation !== void 0 && !this.list().some((model) => model.package?.installationId === installation)) await this.call("remove", { id: installation });
2890
+ }
2891
+ async configure(update) {
2892
+ if (Object.keys(update).some((key) => ![
2893
+ "floatingEnabled",
2894
+ "dialogEnabled",
2895
+ "floatingSize",
2896
+ "dialogSize"
2897
+ ].includes(key))) throw new TypeError("展示设置字段无效");
2898
+ return this.write((current) => ({
2899
+ ...current,
2900
+ ...update
2901
+ }));
2902
+ }
2903
+ /** 必须从独立协议操作调用;展示配置不会隐式开启第三方目录。 */
2904
+ setThirdPartyEnabled(enabled) {
2905
+ return this.write((current) => {
2906
+ const { thirdPartyConsent: _previous, ...appearance } = current;
2907
+ return enabled ? {
2908
+ ...appearance,
2909
+ thirdPartyConsent: {
2910
+ version: ASSISTANT_AVATAR_CONSENT_VERSION,
2911
+ acceptedAt: Date.now()
2912
+ }
2913
+ } : appearance;
2914
+ });
2915
+ }
2916
+ /** 引擎许可与第三方素材协议分开记录;撤销同意不卸载已安装引擎。 */
2917
+ setEngineEnabled(enabled) {
2918
+ return this.write((current) => {
2919
+ const { engineConsent: _previous, ...appearance } = current;
2920
+ return enabled ? {
2921
+ ...appearance,
2922
+ engineConsent: {
2923
+ version: ASSISTANT_AVATAR_ENGINE_CONSENT_VERSION,
2924
+ acceptedAt: Date.now()
2925
+ }
2926
+ } : appearance;
2927
+ });
2928
+ }
2929
+ /** 只读探测;已确认许可后由安装接口写入 CodingNS 自有引擎目录。 */
2930
+ async engineStatus(signal) {
2931
+ return readEngineStatus(await this.call("engineStatus", {}, signal));
2932
+ }
2933
+ async installEngine(signal) {
2934
+ this.requireWritable();
2935
+ this.requireEngineConsent();
2936
+ signal?.throwIfAborted();
2937
+ const result = readEngineStatus(await this.call("installEngine", { engineConsent: this.getAppearance().engineConsent }, signal));
2938
+ signal?.throwIfAborted();
2939
+ if (!result.installed) throw new Error("Live2D 引擎安装后仍未就绪,请重试");
2940
+ invalidateAssistantAvatarRuntime();
2941
+ return result;
2942
+ }
2943
+ async getCatalog(signal) {
2944
+ this.requireConsent();
2945
+ signal?.throwIfAborted();
2946
+ return this.call("catalog", {}, signal);
2947
+ }
2948
+ async previewCatalog(id, revision, lease, signal) {
2949
+ this.requireWritable();
2950
+ this.requireConsent();
2951
+ signal?.throwIfAborted();
2952
+ const result = await this.call("previewCatalog", {
2953
+ id,
2954
+ revision,
2955
+ lease
2956
+ }, signal);
2957
+ validateAssistantAvatarModel(result.model);
2958
+ if (result.lease !== lease) throw new TypeError("临时形象预览标识不一致");
2959
+ return result;
2960
+ }
2961
+ async releasePreview(lease) {
2962
+ await this.call("releasePreview", { lease });
2963
+ }
2964
+ async keepPreview(lease) {
2965
+ await this.call("keepPreview", { lease });
2966
+ }
2967
+ async installCatalog(id, revision, licenseAccepted, signal, lease) {
2968
+ this.requireWritable();
2969
+ this.requireConsent();
2970
+ signal?.throwIfAborted();
2971
+ if (!licenseAccepted) throw new TypeError("请确认所选形象的许可及应用");
2972
+ const installed = await this.call("installCatalog", {
2973
+ id,
2974
+ revision,
2975
+ licenseAccepted,
2976
+ ...lease === void 0 ? {} : { lease }
2977
+ }, signal);
2978
+ return this.registerInstallation(installed, signal, true);
2979
+ }
2980
+ /** 只读清单 JSON;网络/解析/取消失败都不新增形象,不修改当前选择。 */
2981
+ async importPackage(source, adapterId = "auto", signal) {
2982
+ this.requireWritable();
2983
+ signal?.throwIfAborted();
2984
+ if (!isAssistantAvatarSource(source)) throw new TypeError("形象包清单地址无效");
2985
+ const url = new URL(source, this.baseUrl).href;
2986
+ const response = await this.fetchManifest(url, {
2987
+ credentials: "same-origin",
2988
+ ...signal === void 0 ? {} : { signal }
2989
+ });
2990
+ if (!response.ok) throw new Error(`avatar_manifest_http_${response.status}`);
2991
+ const text = await response.text();
2992
+ if (text.length > 262144) throw new TypeError("形象包清单过大");
2993
+ signal?.throwIfAborted();
2994
+ const model = this.adapters.parse(JSON.parse(text), response.url || url, adapterId);
2995
+ await this.insert(model, signal);
2996
+ return model;
2997
+ }
2998
+ /** 新入口在 Host 安装完整素材后才写入形象列表;旧外链导入 API 保持兼容。 */
2999
+ async discover(source, signal) {
3000
+ this.requireWritable();
3001
+ signal?.throwIfAborted();
3002
+ return this.call("discover", { source }, signal);
3003
+ }
3004
+ async installPackage(source, adapterId = "auto", signal) {
3005
+ this.requireWritable();
3006
+ signal?.throwIfAborted();
3007
+ const installed = await this.call("install", {
3008
+ source,
3009
+ adapterId
3010
+ }, signal);
3011
+ return this.registerInstallation(installed, signal);
3012
+ }
3013
+ async registerInstallation(installed, signal, fromCatalog = false) {
3014
+ let replacedInstallation;
3015
+ try {
3016
+ validateAssistantAvatarModel(installed.model);
3017
+ await this.write((current) => {
3018
+ signal?.throwIfAborted();
3019
+ if (fromCatalog && !hasAssistantAvatarConsent(current.thirdPartyConsent)) throw new TypeError("第三方形象列表已关闭");
3020
+ const existing = current.models.find((model) => model.id === installed.model.id);
3021
+ replacedInstallation = existing?.package?.installationId;
3022
+ const sameSource = existing?.package?.manifestUrl !== void 0 && existing.package.manifestUrl === installed.model.package?.manifestUrl;
3023
+ if (existing !== void 0 && existing.package?.installationId !== installed.id && !sameSource) throw new TypeError("形象 ID 已存在,请先移除已有记录");
3024
+ if (installed.model.package?.installationId !== installed.id || !/^[a-f0-9]{64}$/u.test(installed.id)) throw new TypeError("形象安装结果无效");
3025
+ return {
3026
+ ...current,
3027
+ selectedId: installed.model.id,
3028
+ models: existing === void 0 ? [...current.models, installed.model] : current.models.map((model) => model.id === existing.id ? installed.model : model)
3029
+ };
3030
+ });
3031
+ if (replacedInstallation !== void 0 && replacedInstallation !== installed.id && !this.list().some((model) => model.package?.installationId === replacedInstallation)) await this.call("remove", { id: replacedInstallation }).catch(() => void 0);
3032
+ return installed.model;
3033
+ } catch (error) {
3034
+ if (installed.created && !this.list().some((model) => model.package?.installationId === installed.id)) await this.call("remove", { id: installed.id }).catch(() => void 0);
3035
+ throw error;
3036
+ }
3037
+ }
3038
+ async call(action, payload, signal) {
3039
+ if (this.rpc === void 0) throw new Error("形象本地安装服务不可用");
3040
+ const result = await this.rpc.call(require_transport.CODINGNS_RPC_CHANNEL, `avatar/${action}`, payload, signal);
3041
+ if (!result.ok) throw new Error(result.error.message);
3042
+ return result.value;
3043
+ }
3044
+ insert(model, signal) {
3045
+ this.requireCustom(model.id);
3046
+ return this.write((current) => {
3047
+ signal?.throwIfAborted();
3048
+ if (current.models.some((item) => item.id === model.id)) throw new TypeError("形象 ID 已存在,请更新已有形象或使用其他 ID");
3049
+ return {
3050
+ ...current,
3051
+ selectedId: model.id,
3052
+ models: [...current.models, model]
3053
+ };
3054
+ });
3055
+ }
3056
+ write(update) {
3057
+ const task = this.writes.then(() => this.saveAppearance(update));
3058
+ this.writes = task.catch(() => void 0);
3059
+ return task;
3060
+ }
3061
+ async saveAppearance(update) {
3062
+ for (let attempt = 0; attempt < 3; attempt++) {
3063
+ const snapshot = this.requireWritable();
3064
+ const current = normalizeAssistantAppearance(snapshot.value.assistant.appearance);
3065
+ const next = update(current);
3066
+ validateAssistantAppearance(next);
3067
+ if (JSON.stringify(next) === JSON.stringify(current)) return;
3068
+ try {
3069
+ if (!await this.settings.mutate([{
3070
+ op: "set",
3071
+ path: ["assistant", "appearance"],
3072
+ value: next
3073
+ }], snapshot.revision)) throw new Error("形象设置保存被拒绝,请刷新后重试");
3074
+ return;
3075
+ } catch (error) {
3076
+ if (!isSettingsConflict(error) || this.settings.reload === void 0) throw error;
3077
+ if (attempt === 2) throw new Error("形象设置持续被其他操作更新,请稍后重试", { cause: error });
3078
+ await this.settings.reload();
3079
+ }
3080
+ }
3081
+ }
3082
+ requireWritable() {
3083
+ const snapshot = this.settings.getSnapshot();
3084
+ if (snapshot.status !== "ready" || snapshot.value === void 0) throw new Error("形象设置尚未就绪");
3085
+ if (!snapshot.writable) throw new Error("当前形象设置为只读");
3086
+ return snapshot;
3087
+ }
3088
+ requireConsent() {
3089
+ if (!hasAssistantAvatarConsent(this.getAppearance().thirdPartyConsent)) throw new TypeError("请先同意第三方形象使用说明");
3090
+ }
3091
+ requireEngineConsent() {
3092
+ if (!hasAssistantAvatarEngineConsent(this.getAppearance().engineConsent)) throw new TypeError("请先确认 Live2D 引擎许可");
3093
+ }
3094
+ requireModel(current, id) {
3095
+ if (!listAssistantAvatars(current).some((item) => item.id === id)) throw new TypeError("形象不在清单中");
3096
+ }
3097
+ requireCustom(id) {
3098
+ if (getBuiltinAssistantAvatar(id) !== void 0) throw new TypeError("内置形象不可替换或删除");
3099
+ }
3100
+ };
3101
+ /** 引擎状态来自 Host;形状异常按不可用处理,避免把坏数据当成就绪。 */
3102
+ function readEngineStatus(value) {
3103
+ const status = value;
3104
+ if (status === null || typeof status !== "object" || typeof status.installed !== "boolean" || typeof status.version !== "string" || ![
3105
+ "managed",
3106
+ "dependency",
3107
+ "missing"
3108
+ ].includes(status.source)) throw new TypeError("Live2D 引擎状态无效");
3109
+ return {
3110
+ installed: status.installed,
3111
+ version: status.version,
3112
+ source: status.source
3113
+ };
3114
+ }
3115
+ /** 兼容原生异常、RPC 稳定错误码,以及旧 Host 只透传正文的响应。 */
3116
+ function isSettingsConflict(error) {
3117
+ if (error === null || typeof error !== "object") return false;
3118
+ const failure = error;
3119
+ return failure.code === "SETTINGS_CONFLICT" || failure.code === "SettingsConflictError" || failure.name === "SettingsConflictError" || typeof failure.message === "string" && /^settings namespace ".+" changed since it was read \(expected revision /u.test(failure.message);
3120
+ }
3121
+ const managers = /* @__PURE__ */ new WeakMap();
3122
+ function bindAssistantAvatarManager(services, manager) {
3123
+ managers.set(services, manager);
3124
+ }
3125
+ function getAssistantAvatarManager(services) {
3126
+ let manager = managers.get(services);
3127
+ if (manager === void 0) {
3128
+ manager = new AssistantAvatarManager(services.settings, void 0, void 0, void 0, services.rpc, (id) => getAssistantAvatarPortraitService(services).removeModel(id));
3129
+ managers.set(services, manager);
3130
+ }
3131
+ return manager;
3132
+ }
3133
+ /** 扩展应将注销函数登记到 context.resources,已导入的数据不会随适配器注销丢失。 */
3134
+ function registerAssistantAvatarAdapter(services, adapter) {
3135
+ return getAssistantAvatarManager(services).adapters.register(adapter);
3136
+ }
3137
+ //#endregion
3138
+ Object.defineProperty(exports, "ASSISTANT_AVATAR_CONSENT_VERSION", {
3139
+ enumerable: true,
3140
+ get: function() {
3141
+ return ASSISTANT_AVATAR_CONSENT_VERSION;
3142
+ }
3143
+ });
3144
+ Object.defineProperty(exports, "ASSISTANT_AVATAR_ENGINE_CONSENT_VERSION", {
3145
+ enumerable: true,
3146
+ get: function() {
3147
+ return ASSISTANT_AVATAR_ENGINE_CONSENT_VERSION;
3148
+ }
3149
+ });
3150
+ Object.defineProperty(exports, "ASSISTANT_PERSONALITY_MAX_CHARS", {
3151
+ enumerable: true,
3152
+ get: function() {
3153
+ return ASSISTANT_PERSONALITY_MAX_CHARS;
3154
+ }
3155
+ });
3156
+ Object.defineProperty(exports, "ASSISTANT_TTS_PARAMETER_LIMITS", {
3157
+ enumerable: true,
3158
+ get: function() {
3159
+ return ASSISTANT_TTS_PARAMETER_LIMITS;
3160
+ }
3161
+ });
3162
+ Object.defineProperty(exports, "ASSISTANT_TTS_PATH", {
3163
+ enumerable: true,
3164
+ get: function() {
3165
+ return ASSISTANT_TTS_PATH;
3166
+ }
3167
+ });
3168
+ Object.defineProperty(exports, "ASSISTANT_VOICE_SITES", {
3169
+ enumerable: true,
3170
+ get: function() {
3171
+ return ASSISTANT_VOICE_SITES;
3172
+ }
3173
+ });
3174
+ Object.defineProperty(exports, "AssistantAvatarAdapterRegistry", {
3175
+ enumerable: true,
3176
+ get: function() {
3177
+ return AssistantAvatarAdapterRegistry;
3178
+ }
3179
+ });
3180
+ Object.defineProperty(exports, "AssistantAvatarLoading", {
3181
+ enumerable: true,
3182
+ get: function() {
3183
+ return AssistantAvatarLoading;
3184
+ }
3185
+ });
3186
+ Object.defineProperty(exports, "AssistantAvatarManager", {
3187
+ enumerable: true,
3188
+ get: function() {
3189
+ return AssistantAvatarManager;
3190
+ }
3191
+ });
3192
+ Object.defineProperty(exports, "AssistantAvatarPortrait", {
3193
+ enumerable: true,
3194
+ get: function() {
3195
+ return AssistantAvatarPortrait;
3196
+ }
3197
+ });
3198
+ Object.defineProperty(exports, "AssistantAvatarPortraitService", {
3199
+ enumerable: true,
3200
+ get: function() {
3201
+ return AssistantAvatarPortraitService;
3202
+ }
3203
+ });
3204
+ Object.defineProperty(exports, "AssistantAvatarSlot", {
3205
+ enumerable: true,
3206
+ get: function() {
3207
+ return AssistantAvatarSlot;
3208
+ }
3209
+ });
3210
+ Object.defineProperty(exports, "BUILTIN_ASSISTANT_AVATAR", {
3211
+ enumerable: true,
3212
+ get: function() {
3213
+ return BUILTIN_ASSISTANT_AVATAR;
3214
+ }
3215
+ });
3216
+ Object.defineProperty(exports, "DEFAULT_ASSISTANT_TTS_PARAMETERS", {
3217
+ enumerable: true,
3218
+ get: function() {
3219
+ return DEFAULT_ASSISTANT_TTS_PARAMETERS;
3220
+ }
3221
+ });
3222
+ Object.defineProperty(exports, "MOSS_BUILTIN_VOICES", {
3223
+ enumerable: true,
3224
+ get: function() {
3225
+ return MOSS_BUILTIN_VOICES;
3226
+ }
3227
+ });
3228
+ Object.defineProperty(exports, "accepted", {
3229
+ enumerable: true,
3230
+ get: function() {
3231
+ return accepted;
3232
+ }
3233
+ });
3234
+ Object.defineProperty(exports, "assistantAvatarPortraitKey", {
3235
+ enumerable: true,
3236
+ get: function() {
3237
+ return assistantAvatarPortraitKey;
3238
+ }
3239
+ });
3240
+ Object.defineProperty(exports, "assistantAvatarPortraitOverrideStore", {
3241
+ enumerable: true,
3242
+ get: function() {
3243
+ return assistantAvatarPortraitOverrideStore;
3244
+ }
3245
+ });
3246
+ Object.defineProperty(exports, "bindAssistantAvatarManager", {
3247
+ enumerable: true,
3248
+ get: function() {
3249
+ return bindAssistantAvatarManager;
3250
+ }
3251
+ });
3252
+ Object.defineProperty(exports, "bindAssistantAvatarPortraitService", {
3253
+ enumerable: true,
3254
+ get: function() {
3255
+ return bindAssistantAvatarPortraitService;
3256
+ }
3257
+ });
3258
+ Object.defineProperty(exports, "bindAssistantAvatarRegistry", {
3259
+ enumerable: true,
3260
+ get: function() {
3261
+ return bindAssistantAvatarRegistry;
3262
+ }
3263
+ });
3264
+ Object.defineProperty(exports, "builtinAssistantAvatarLabelKey", {
3265
+ enumerable: true,
3266
+ get: function() {
3267
+ return builtinAssistantAvatarLabelKey;
3268
+ }
3269
+ });
3270
+ Object.defineProperty(exports, "captureAssistantAvatarPreview", {
3271
+ enumerable: true,
3272
+ get: function() {
3273
+ return captureAssistantAvatarPreview;
3274
+ }
3275
+ });
3276
+ Object.defineProperty(exports, "clampAssistantAvatarPosition", {
3277
+ enumerable: true,
3278
+ get: function() {
3279
+ return clampAssistantAvatarPosition;
3280
+ }
3281
+ });
3282
+ Object.defineProperty(exports, "cropAssistantAvatarPortrait", {
3283
+ enumerable: true,
3284
+ get: function() {
3285
+ return cropAssistantAvatarPortrait;
3286
+ }
3287
+ });
3288
+ Object.defineProperty(exports, "fillAssistantAvatarButton", {
3289
+ enumerable: true,
3290
+ get: function() {
3291
+ return fillAssistantAvatarButton;
3292
+ }
3293
+ });
3294
+ Object.defineProperty(exports, "getAssistantAvatarManager", {
3295
+ enumerable: true,
3296
+ get: function() {
3297
+ return getAssistantAvatarManager;
3298
+ }
3299
+ });
3300
+ Object.defineProperty(exports, "getAssistantAvatarPortraitService", {
3301
+ enumerable: true,
3302
+ get: function() {
3303
+ return getAssistantAvatarPortraitService;
3304
+ }
3305
+ });
3306
+ Object.defineProperty(exports, "getAssistantAvatarPreset", {
3307
+ enumerable: true,
3308
+ get: function() {
3309
+ return getAssistantAvatarPreset;
3310
+ }
3311
+ });
3312
+ Object.defineProperty(exports, "getAssistantAvatarRegistry", {
3313
+ enumerable: true,
3314
+ get: function() {
3315
+ return getAssistantAvatarRegistry;
3316
+ }
3317
+ });
3318
+ Object.defineProperty(exports, "getBuiltinAssistantAvatar", {
3319
+ enumerable: true,
3320
+ get: function() {
3321
+ return getBuiltinAssistantAvatar;
3322
+ }
3323
+ });
3324
+ Object.defineProperty(exports, "getGlobalVoiceAdapter", {
3325
+ enumerable: true,
3326
+ get: function() {
3327
+ return getGlobalVoiceAdapter;
3328
+ }
3329
+ });
3330
+ Object.defineProperty(exports, "hasAssistantAvatarConsent", {
3331
+ enumerable: true,
3332
+ get: function() {
3333
+ return hasAssistantAvatarConsent;
3334
+ }
3335
+ });
3336
+ Object.defineProperty(exports, "hasAssistantAvatarEngineConsent", {
3337
+ enumerable: true,
3338
+ get: function() {
3339
+ return hasAssistantAvatarEngineConsent;
3340
+ }
3341
+ });
3342
+ Object.defineProperty(exports, "initialAssistantAvatarPortraitArea", {
3343
+ enumerable: true,
3344
+ get: function() {
3345
+ return initialAssistantAvatarPortraitArea;
3346
+ }
3347
+ });
3348
+ Object.defineProperty(exports, "isAssistantAvatarSource", {
3349
+ enumerable: true,
3350
+ get: function() {
3351
+ return isAssistantAvatarSource;
3352
+ }
3353
+ });
3354
+ Object.defineProperty(exports, "listAssistantAvatars", {
3355
+ enumerable: true,
3356
+ get: function() {
3357
+ return listAssistantAvatars;
3358
+ }
3359
+ });
3360
+ Object.defineProperty(exports, "normalizeAssistantAppearance", {
3361
+ enumerable: true,
3362
+ get: function() {
3363
+ return normalizeAssistantAppearance;
3364
+ }
3365
+ });
3366
+ Object.defineProperty(exports, "readAssistantPersonality", {
3367
+ enumerable: true,
3368
+ get: function() {
3369
+ return readAssistantPersonality;
3370
+ }
3371
+ });
3372
+ Object.defineProperty(exports, "readAssistantProfile", {
3373
+ enumerable: true,
3374
+ get: function() {
3375
+ return readAssistantProfile;
3376
+ }
3377
+ });
3378
+ Object.defineProperty(exports, "readAssistantTtsParameters", {
3379
+ enumerable: true,
3380
+ get: function() {
3381
+ return readAssistantTtsParameters;
3382
+ }
3383
+ });
3384
+ Object.defineProperty(exports, "readAssistantTtsSettings", {
3385
+ enumerable: true,
3386
+ get: function() {
3387
+ return readAssistantTtsSettings;
3388
+ }
3389
+ });
3390
+ Object.defineProperty(exports, "registerAssistantAvatarAdapter", {
3391
+ enumerable: true,
3392
+ get: function() {
3393
+ return registerAssistantAvatarAdapter;
3394
+ }
3395
+ });
3396
+ Object.defineProperty(exports, "registerAssistantAvatarRenderer", {
3397
+ enumerable: true,
3398
+ get: function() {
3399
+ return registerAssistantAvatarRenderer;
3400
+ }
3401
+ });
3402
+ Object.defineProperty(exports, "registerGlobalVoiceAdapter", {
3403
+ enumerable: true,
3404
+ get: function() {
3405
+ return registerGlobalVoiceAdapter;
3406
+ }
3407
+ });
3408
+ Object.defineProperty(exports, "resolveAssistantAvatarAsset", {
3409
+ enumerable: true,
3410
+ get: function() {
3411
+ return resolveAssistantAvatarAsset;
3412
+ }
3413
+ });
3414
+ Object.defineProperty(exports, "resolveAssistantAvatarState", {
3415
+ enumerable: true,
3416
+ get: function() {
3417
+ return resolveAssistantAvatarState;
3418
+ }
3419
+ });
3420
+ Object.defineProperty(exports, "sameSettingsSnapshot", {
3421
+ enumerable: true,
3422
+ get: function() {
3423
+ return sameSettingsSnapshot;
3424
+ }
3425
+ });
3426
+ Object.defineProperty(exports, "sameSettingsValue", {
3427
+ enumerable: true,
3428
+ get: function() {
3429
+ return sameSettingsValue;
3430
+ }
3431
+ });
3432
+ Object.defineProperty(exports, "selectedAssistantAvatar", {
3433
+ enumerable: true,
3434
+ get: function() {
3435
+ return selectedAssistantAvatar;
3436
+ }
3437
+ });
3438
+ Object.defineProperty(exports, "useAssistantAvatarPortrait", {
3439
+ enumerable: true,
3440
+ get: function() {
3441
+ return useAssistantAvatarPortrait;
3442
+ }
3443
+ });
3444
+ Object.defineProperty(exports, "validateAssistantTtsParameters", {
3445
+ enumerable: true,
3446
+ get: function() {
3447
+ return validateAssistantTtsParameters;
3448
+ }
3449
+ });
3450
+ return module.exports;
3451
+ }
3452
+ });
3453
+
3454
+ //# sourceMappingURL=client.manager.js.map