@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,4526 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@jingyi0605/codingns4dsh",
3
+ chunk: "client.assistant-configuration-view.js",
4
+ factory: (require) => {
5
+ var module = { exports: {} };
6
+ var exports = module.exports;
7
+ const require_rolldown_runtime = require("@jingyi0605/codingns4dsh/client.rolldown-runtime.js");
8
+ const require_transport = require("@jingyi0605/codingns4dsh/client.transport.js");
9
+ const require_assistant_prompts = require("@jingyi0605/codingns4dsh/client.assistant-prompts.js");
10
+ const require_theme = require("@jingyi0605/codingns4dsh/client.theme.js");
11
+ const require_manager = require("@jingyi0605/codingns4dsh/client.manager.js");
12
+ const require_primitives_adapter = require("@jingyi0605/codingns4dsh/client.primitives-adapter.js");
13
+ const require_react_dom$1 = require("@jingyi0605/codingns4dsh/client.react-dom.js");
14
+ const require_assistant_prompt_editor = require("@jingyi0605/codingns4dsh/client.assistant-prompt-editor.js");
15
+ const require_assistant_workbench = require("@jingyi0605/codingns4dsh/client.assistant-workbench.js");
16
+ let react = require("react");
17
+ react = require_rolldown_runtime.__toESM(react, 1);
18
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
19
+ //#region node_modules/.pnpm/tslib@2.8.1/node_modules/tslib/tslib.es6.mjs
20
+ /******************************************************************************
21
+ Copyright (c) Microsoft Corporation.
22
+
23
+ Permission to use, copy, modify, and/or distribute this software for any
24
+ purpose with or without fee is hereby granted.
25
+
26
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
27
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
28
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
29
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
30
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
31
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
32
+ PERFORMANCE OF THIS SOFTWARE.
33
+ ***************************************************************************** */
34
+ var extendStatics = function(d, b) {
35
+ extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function(d, b) {
36
+ d.__proto__ = b;
37
+ } || function(d, b) {
38
+ for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p];
39
+ };
40
+ return extendStatics(d, b);
41
+ };
42
+ function __extends(d, b) {
43
+ if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
44
+ extendStatics(d, b);
45
+ function __() {
46
+ this.constructor = d;
47
+ }
48
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
49
+ }
50
+ var __assign = function() {
51
+ __assign = Object.assign || function __assign(t) {
52
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
53
+ s = arguments[i];
54
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
55
+ }
56
+ return t;
57
+ };
58
+ return __assign.apply(this, arguments);
59
+ };
60
+ //#endregion
61
+ //#region node_modules/.pnpm/normalize-wheel@1.0.1/node_modules/normalize-wheel/src/UserAgent_DEPRECATED.js
62
+ var require_UserAgent_DEPRECATED = /* @__PURE__ */ require_rolldown_runtime.__commonJSMin(((exports, module) => {
63
+ /**
64
+ * Copyright 2004-present Facebook. All Rights Reserved.
65
+ *
66
+ * @providesModule UserAgent_DEPRECATED
67
+ */
68
+ /**
69
+ * Provides entirely client-side User Agent and OS detection. You should prefer
70
+ * the non-deprecated UserAgent module when possible, which exposes our
71
+ * authoritative server-side PHP-based detection to the client.
72
+ *
73
+ * Usage is straightforward:
74
+ *
75
+ * if (UserAgent_DEPRECATED.ie()) {
76
+ * // IE
77
+ * }
78
+ *
79
+ * You can also do version checks:
80
+ *
81
+ * if (UserAgent_DEPRECATED.ie() >= 7) {
82
+ * // IE7 or better
83
+ * }
84
+ *
85
+ * The browser functions will return NaN if the browser does not match, so
86
+ * you can also do version compares the other way:
87
+ *
88
+ * if (UserAgent_DEPRECATED.ie() < 7) {
89
+ * // IE6 or worse
90
+ * }
91
+ *
92
+ * Note that the version is a float and may include a minor version number,
93
+ * so you should always use range operators to perform comparisons, not
94
+ * strict equality.
95
+ *
96
+ * **Note:** You should **strongly** prefer capability detection to browser
97
+ * version detection where it's reasonable:
98
+ *
99
+ * http://www.quirksmode.org/js/support.html
100
+ *
101
+ * Further, we have a large number of mature wrapper functions and classes
102
+ * which abstract away many browser irregularities. Check the documentation,
103
+ * grep for things, or ask on javascript@lists.facebook.com before writing yet
104
+ * another copy of "event || window.event".
105
+ *
106
+ */
107
+ var _populated = false;
108
+ var _ie;
109
+ var _firefox;
110
+ var _opera;
111
+ var _webkit;
112
+ var _chrome;
113
+ var _ie_real_version;
114
+ var _osx;
115
+ var _windows;
116
+ var _linux;
117
+ var _android;
118
+ var _win64;
119
+ var _iphone;
120
+ var _ipad;
121
+ var _native;
122
+ var _mobile;
123
+ function _populate() {
124
+ if (_populated) return;
125
+ _populated = true;
126
+ var uas = navigator.userAgent;
127
+ var agent = /(?:MSIE.(\d+\.\d+))|(?:(?:Firefox|GranParadiso|Iceweasel).(\d+\.\d+))|(?:Opera(?:.+Version.|.)(\d+\.\d+))|(?:AppleWebKit.(\d+(?:\.\d+)?))|(?:Trident\/\d+\.\d+.*rv:(\d+\.\d+))/.exec(uas);
128
+ var os = /(Mac OS X)|(Windows)|(Linux)/.exec(uas);
129
+ _iphone = /\b(iPhone|iP[ao]d)/.exec(uas);
130
+ _ipad = /\b(iP[ao]d)/.exec(uas);
131
+ _android = /Android/i.exec(uas);
132
+ _native = /FBAN\/\w+;/i.exec(uas);
133
+ _mobile = /Mobile/i.exec(uas);
134
+ _win64 = !!/Win64/.exec(uas);
135
+ if (agent) {
136
+ _ie = agent[1] ? parseFloat(agent[1]) : agent[5] ? parseFloat(agent[5]) : NaN;
137
+ if (_ie && document && document.documentMode) _ie = document.documentMode;
138
+ var trident = /(?:Trident\/(\d+.\d+))/.exec(uas);
139
+ _ie_real_version = trident ? parseFloat(trident[1]) + 4 : _ie;
140
+ _firefox = agent[2] ? parseFloat(agent[2]) : NaN;
141
+ _opera = agent[3] ? parseFloat(agent[3]) : NaN;
142
+ _webkit = agent[4] ? parseFloat(agent[4]) : NaN;
143
+ if (_webkit) {
144
+ agent = /(?:Chrome\/(\d+\.\d+))/.exec(uas);
145
+ _chrome = agent && agent[1] ? parseFloat(agent[1]) : NaN;
146
+ } else _chrome = NaN;
147
+ } else _ie = _firefox = _opera = _chrome = _webkit = NaN;
148
+ if (os) {
149
+ if (os[1]) {
150
+ var ver = /(?:Mac OS X (\d+(?:[._]\d+)?))/.exec(uas);
151
+ _osx = ver ? parseFloat(ver[1].replace("_", ".")) : true;
152
+ } else _osx = false;
153
+ _windows = !!os[2];
154
+ _linux = !!os[3];
155
+ } else _osx = _windows = _linux = false;
156
+ }
157
+ var UserAgent_DEPRECATED = {
158
+ /**
159
+ * Check if the UA is Internet Explorer.
160
+ *
161
+ *
162
+ * @return float|NaN Version number (if match) or NaN.
163
+ */
164
+ ie: function() {
165
+ return _populate() || _ie;
166
+ },
167
+ /**
168
+ * Check if we're in Internet Explorer compatibility mode.
169
+ *
170
+ * @return bool true if in compatibility mode, false if
171
+ * not compatibility mode or not ie
172
+ */
173
+ ieCompatibilityMode: function() {
174
+ return _populate() || _ie_real_version > _ie;
175
+ },
176
+ /**
177
+ * Whether the browser is 64-bit IE. Really, this is kind of weak sauce; we
178
+ * only need this because Skype can't handle 64-bit IE yet. We need to remove
179
+ * this when we don't need it -- tracked by #601957.
180
+ */
181
+ ie64: function() {
182
+ return UserAgent_DEPRECATED.ie() && _win64;
183
+ },
184
+ /**
185
+ * Check if the UA is Firefox.
186
+ *
187
+ *
188
+ * @return float|NaN Version number (if match) or NaN.
189
+ */
190
+ firefox: function() {
191
+ return _populate() || _firefox;
192
+ },
193
+ /**
194
+ * Check if the UA is Opera.
195
+ *
196
+ *
197
+ * @return float|NaN Version number (if match) or NaN.
198
+ */
199
+ opera: function() {
200
+ return _populate() || _opera;
201
+ },
202
+ /**
203
+ * Check if the UA is WebKit.
204
+ *
205
+ *
206
+ * @return float|NaN Version number (if match) or NaN.
207
+ */
208
+ webkit: function() {
209
+ return _populate() || _webkit;
210
+ },
211
+ /**
212
+ * For Push
213
+ * WILL BE REMOVED VERY SOON. Use UserAgent_DEPRECATED.webkit
214
+ */
215
+ safari: function() {
216
+ return UserAgent_DEPRECATED.webkit();
217
+ },
218
+ /**
219
+ * Check if the UA is a Chrome browser.
220
+ *
221
+ *
222
+ * @return float|NaN Version number (if match) or NaN.
223
+ */
224
+ chrome: function() {
225
+ return _populate() || _chrome;
226
+ },
227
+ /**
228
+ * Check if the user is running Windows.
229
+ *
230
+ * @return bool `true' if the user's OS is Windows.
231
+ */
232
+ windows: function() {
233
+ return _populate() || _windows;
234
+ },
235
+ /**
236
+ * Check if the user is running Mac OS X.
237
+ *
238
+ * @return float|bool Returns a float if a version number is detected,
239
+ * otherwise true/false.
240
+ */
241
+ osx: function() {
242
+ return _populate() || _osx;
243
+ },
244
+ /**
245
+ * Check if the user is running Linux.
246
+ *
247
+ * @return bool `true' if the user's OS is some flavor of Linux.
248
+ */
249
+ linux: function() {
250
+ return _populate() || _linux;
251
+ },
252
+ /**
253
+ * Check if the user is running on an iPhone or iPod platform.
254
+ *
255
+ * @return bool `true' if the user is running some flavor of the
256
+ * iPhone OS.
257
+ */
258
+ iphone: function() {
259
+ return _populate() || _iphone;
260
+ },
261
+ mobile: function() {
262
+ return _populate() || _iphone || _ipad || _android || _mobile;
263
+ },
264
+ nativeApp: function() {
265
+ return _populate() || _native;
266
+ },
267
+ android: function() {
268
+ return _populate() || _android;
269
+ },
270
+ ipad: function() {
271
+ return _populate() || _ipad;
272
+ }
273
+ };
274
+ module.exports = UserAgent_DEPRECATED;
275
+ }));
276
+ //#endregion
277
+ //#region node_modules/.pnpm/normalize-wheel@1.0.1/node_modules/normalize-wheel/src/ExecutionEnvironment.js
278
+ /**
279
+ * Copyright (c) 2015, Facebook, Inc.
280
+ * All rights reserved.
281
+ *
282
+ * This source code is licensed under the BSD-style license found in the
283
+ * LICENSE file in the root directory of this source tree. An additional grant
284
+ * of patent rights can be found in the PATENTS file in the same directory.
285
+ *
286
+ * @providesModule ExecutionEnvironment
287
+ */
288
+ var require_ExecutionEnvironment = /* @__PURE__ */ require_rolldown_runtime.__commonJSMin(((exports, module) => {
289
+ var canUseDOM = !!(typeof window !== "undefined" && window.document && window.document.createElement);
290
+ module.exports = {
291
+ canUseDOM,
292
+ canUseWorkers: typeof Worker !== "undefined",
293
+ canUseEventListeners: canUseDOM && !!(window.addEventListener || window.attachEvent),
294
+ canUseViewport: canUseDOM && !!window.screen,
295
+ isInWorker: !canUseDOM
296
+ };
297
+ }));
298
+ //#endregion
299
+ //#region node_modules/.pnpm/normalize-wheel@1.0.1/node_modules/normalize-wheel/src/isEventSupported.js
300
+ /**
301
+ * Copyright 2013-2015, Facebook, Inc.
302
+ * All rights reserved.
303
+ *
304
+ * This source code is licensed under the BSD-style license found in the
305
+ * LICENSE file in the root directory of this source tree. An additional grant
306
+ * of patent rights can be found in the PATENTS file in the same directory.
307
+ *
308
+ * @providesModule isEventSupported
309
+ */
310
+ var require_isEventSupported = /* @__PURE__ */ require_rolldown_runtime.__commonJSMin(((exports, module) => {
311
+ var ExecutionEnvironment = require_ExecutionEnvironment();
312
+ var useHasFeature;
313
+ if (ExecutionEnvironment.canUseDOM) useHasFeature = document.implementation && document.implementation.hasFeature && document.implementation.hasFeature("", "") !== true;
314
+ /**
315
+ * Checks if an event is supported in the current execution environment.
316
+ *
317
+ * NOTE: This will not work correctly for non-generic events such as `change`,
318
+ * `reset`, `load`, `error`, and `select`.
319
+ *
320
+ * Borrows from Modernizr.
321
+ *
322
+ * @param {string} eventNameSuffix Event name, e.g. "click".
323
+ * @param {?boolean} capture Check if the capture phase is supported.
324
+ * @return {boolean} True if the event is supported.
325
+ * @internal
326
+ * @license Modernizr 3.0.0pre (Custom Build) | MIT
327
+ */
328
+ function isEventSupported(eventNameSuffix, capture) {
329
+ if (!ExecutionEnvironment.canUseDOM || capture && !("addEventListener" in document)) return false;
330
+ var eventName = "on" + eventNameSuffix;
331
+ var isSupported = eventName in document;
332
+ if (!isSupported) {
333
+ var element = document.createElement("div");
334
+ element.setAttribute(eventName, "return;");
335
+ isSupported = typeof element[eventName] === "function";
336
+ }
337
+ if (!isSupported && useHasFeature && eventNameSuffix === "wheel") isSupported = document.implementation.hasFeature("Events.wheel", "3.0");
338
+ return isSupported;
339
+ }
340
+ module.exports = isEventSupported;
341
+ }));
342
+ //#endregion
343
+ //#region node_modules/.pnpm/normalize-wheel@1.0.1/node_modules/normalize-wheel/src/normalizeWheel.js
344
+ /**
345
+ * Copyright (c) 2015, Facebook, Inc.
346
+ * All rights reserved.
347
+ *
348
+ * This source code is licensed under the BSD-style license found in the
349
+ * LICENSE file in the root directory of this source tree. An additional grant
350
+ * of patent rights can be found in the PATENTS file in the same directory.
351
+ *
352
+ * @providesModule normalizeWheel
353
+ * @typechecks
354
+ */
355
+ var require_normalizeWheel = /* @__PURE__ */ require_rolldown_runtime.__commonJSMin(((exports, module) => {
356
+ var UserAgent_DEPRECATED = require_UserAgent_DEPRECATED();
357
+ var isEventSupported = require_isEventSupported();
358
+ var PIXEL_STEP = 10;
359
+ var LINE_HEIGHT = 40;
360
+ var PAGE_HEIGHT = 800;
361
+ /**
362
+ * Mouse wheel (and 2-finger trackpad) support on the web sucks. It is
363
+ * complicated, thus this doc is long and (hopefully) detailed enough to answer
364
+ * your questions.
365
+ *
366
+ * If you need to react to the mouse wheel in a predictable way, this code is
367
+ * like your bestest friend. * hugs *
368
+ *
369
+ * As of today, there are 4 DOM event types you can listen to:
370
+ *
371
+ * 'wheel' -- Chrome(31+), FF(17+), IE(9+)
372
+ * 'mousewheel' -- Chrome, IE(6+), Opera, Safari
373
+ * 'MozMousePixelScroll' -- FF(3.5 only!) (2010-2013) -- don't bother!
374
+ * 'DOMMouseScroll' -- FF(0.9.7+) since 2003
375
+ *
376
+ * So what to do? The is the best:
377
+ *
378
+ * normalizeWheel.getEventType();
379
+ *
380
+ * In your event callback, use this code to get sane interpretation of the
381
+ * deltas. This code will return an object with properties:
382
+ *
383
+ * spinX -- normalized spin speed (use for zoom) - x plane
384
+ * spinY -- " - y plane
385
+ * pixelX -- normalized distance (to pixels) - x plane
386
+ * pixelY -- " - y plane
387
+ *
388
+ * Wheel values are provided by the browser assuming you are using the wheel to
389
+ * scroll a web page by a number of lines or pixels (or pages). Values can vary
390
+ * significantly on different platforms and browsers, forgetting that you can
391
+ * scroll at different speeds. Some devices (like trackpads) emit more events
392
+ * at smaller increments with fine granularity, and some emit massive jumps with
393
+ * linear speed or acceleration.
394
+ *
395
+ * This code does its best to normalize the deltas for you:
396
+ *
397
+ * - spin is trying to normalize how far the wheel was spun (or trackpad
398
+ * dragged). This is super useful for zoom support where you want to
399
+ * throw away the chunky scroll steps on the PC and make those equal to
400
+ * the slow and smooth tiny steps on the Mac. Key data: This code tries to
401
+ * resolve a single slow step on a wheel to 1.
402
+ *
403
+ * - pixel is normalizing the desired scroll delta in pixel units. You'll
404
+ * get the crazy differences between browsers, but at least it'll be in
405
+ * pixels!
406
+ *
407
+ * - positive value indicates scrolling DOWN/RIGHT, negative UP/LEFT. This
408
+ * should translate to positive value zooming IN, negative zooming OUT.
409
+ * This matches the newer 'wheel' event.
410
+ *
411
+ * Why are there spinX, spinY (or pixels)?
412
+ *
413
+ * - spinX is a 2-finger side drag on the trackpad, and a shift + wheel turn
414
+ * with a mouse. It results in side-scrolling in the browser by default.
415
+ *
416
+ * - spinY is what you expect -- it's the classic axis of a mouse wheel.
417
+ *
418
+ * - I dropped spinZ/pixelZ. It is supported by the DOM 3 'wheel' event and
419
+ * probably is by browsers in conjunction with fancy 3D controllers .. but
420
+ * you know.
421
+ *
422
+ * Implementation info:
423
+ *
424
+ * Examples of 'wheel' event if you scroll slowly (down) by one step with an
425
+ * average mouse:
426
+ *
427
+ * OS X + Chrome (mouse) - 4 pixel delta (wheelDelta -120)
428
+ * OS X + Safari (mouse) - N/A pixel delta (wheelDelta -12)
429
+ * OS X + Firefox (mouse) - 0.1 line delta (wheelDelta N/A)
430
+ * Win8 + Chrome (mouse) - 100 pixel delta (wheelDelta -120)
431
+ * Win8 + Firefox (mouse) - 3 line delta (wheelDelta -120)
432
+ *
433
+ * On the trackpad:
434
+ *
435
+ * OS X + Chrome (trackpad) - 2 pixel delta (wheelDelta -6)
436
+ * OS X + Firefox (trackpad) - 1 pixel delta (wheelDelta N/A)
437
+ *
438
+ * On other/older browsers.. it's more complicated as there can be multiple and
439
+ * also missing delta values.
440
+ *
441
+ * The 'wheel' event is more standard:
442
+ *
443
+ * http://www.w3.org/TR/DOM-Level-3-Events/#events-wheelevents
444
+ *
445
+ * The basics is that it includes a unit, deltaMode (pixels, lines, pages), and
446
+ * deltaX, deltaY and deltaZ. Some browsers provide other values to maintain
447
+ * backward compatibility with older events. Those other values help us
448
+ * better normalize spin speed. Example of what the browsers provide:
449
+ *
450
+ * | event.wheelDelta | event.detail
451
+ * ------------------+------------------+--------------
452
+ * Safari v5/OS X | -120 | 0
453
+ * Safari v5/Win7 | -120 | 0
454
+ * Chrome v17/OS X | -120 | 0
455
+ * Chrome v17/Win7 | -120 | 0
456
+ * IE9/Win7 | -120 | undefined
457
+ * Firefox v4/OS X | undefined | 1
458
+ * Firefox v4/Win7 | undefined | 3
459
+ *
460
+ */
461
+ function normalizeWheel(event) {
462
+ var sX = 0, sY = 0, pX = 0, pY = 0;
463
+ if ("detail" in event) sY = event.detail;
464
+ if ("wheelDelta" in event) sY = -event.wheelDelta / 120;
465
+ if ("wheelDeltaY" in event) sY = -event.wheelDeltaY / 120;
466
+ if ("wheelDeltaX" in event) sX = -event.wheelDeltaX / 120;
467
+ if ("axis" in event && event.axis === event.HORIZONTAL_AXIS) {
468
+ sX = sY;
469
+ sY = 0;
470
+ }
471
+ pX = sX * PIXEL_STEP;
472
+ pY = sY * PIXEL_STEP;
473
+ if ("deltaY" in event) pY = event.deltaY;
474
+ if ("deltaX" in event) pX = event.deltaX;
475
+ if ((pX || pY) && event.deltaMode) {
476
+ if (event.deltaMode == 1) {
477
+ pX *= LINE_HEIGHT;
478
+ pY *= LINE_HEIGHT;
479
+ } else {
480
+ pX *= PAGE_HEIGHT;
481
+ pY *= PAGE_HEIGHT;
482
+ }
483
+ }
484
+ if (pX && !sX) sX = pX < 1 ? -1 : 1;
485
+ if (pY && !sY) sY = pY < 1 ? -1 : 1;
486
+ return {
487
+ spinX: sX,
488
+ spinY: sY,
489
+ pixelX: pX,
490
+ pixelY: pY
491
+ };
492
+ }
493
+ /**
494
+ * The best combination if you prefer spinX + spinY normalization. It favors
495
+ * the older DOMMouseScroll for Firefox, as FF does not include wheelDelta with
496
+ * 'wheel' event, making spin speed determination impossible.
497
+ */
498
+ normalizeWheel.getEventType = function() {
499
+ return UserAgent_DEPRECATED.firefox() ? "DOMMouseScroll" : isEventSupported("wheel") ? "wheel" : "mousewheel";
500
+ };
501
+ module.exports = normalizeWheel;
502
+ }));
503
+ //#endregion
504
+ //#region node_modules/.pnpm/normalize-wheel@1.0.1/node_modules/normalize-wheel/index.js
505
+ var require_normalize_wheel = /* @__PURE__ */ require_rolldown_runtime.__commonJSMin(((exports, module) => {
506
+ module.exports = require_normalizeWheel();
507
+ }));
508
+ //#endregion
509
+ //#region node_modules/.pnpm/react-easy-crop@5.5.6_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/react-easy-crop/index.module.mjs
510
+ var import_normalize_wheel = /* @__PURE__ */ require_rolldown_runtime.__toESM(require_normalize_wheel(), 1);
511
+ /**
512
+ * Compute the dimension of the crop area based on media size,
513
+ * aspect ratio and optionally rotation
514
+ */
515
+ function getCropSize(mediaWidth, mediaHeight, containerWidth, containerHeight, aspect, rotation) {
516
+ if (rotation === void 0) rotation = 0;
517
+ var _a = rotateSize(mediaWidth, mediaHeight, rotation), width = _a.width, height = _a.height;
518
+ var fittingWidth = Math.min(width, containerWidth);
519
+ var fittingHeight = Math.min(height, containerHeight);
520
+ if (fittingWidth > fittingHeight * aspect) return {
521
+ width: fittingHeight * aspect,
522
+ height: fittingHeight
523
+ };
524
+ return {
525
+ width: fittingWidth,
526
+ height: fittingWidth / aspect
527
+ };
528
+ }
529
+ /**
530
+ * Compute media zoom.
531
+ * We fit the media into the container with "max-width: 100%; max-height: 100%;"
532
+ */
533
+ function getMediaZoom(mediaSize) {
534
+ return mediaSize.width > mediaSize.height ? mediaSize.width / mediaSize.naturalWidth : mediaSize.height / mediaSize.naturalHeight;
535
+ }
536
+ /**
537
+ * Ensure a new media position stays in the crop area.
538
+ */
539
+ function restrictPosition(position, mediaSize, cropSize, zoom, rotation) {
540
+ if (rotation === void 0) rotation = 0;
541
+ var _a = rotateSize(mediaSize.width, mediaSize.height, rotation), width = _a.width, height = _a.height;
542
+ return {
543
+ x: restrictPositionCoord(position.x, width, cropSize.width, zoom),
544
+ y: restrictPositionCoord(position.y, height, cropSize.height, zoom)
545
+ };
546
+ }
547
+ function restrictPositionCoord(position, mediaSize, cropSize, zoom) {
548
+ var maxPosition = mediaSize * zoom / 2 - cropSize / 2;
549
+ return clamp(position, -maxPosition, maxPosition);
550
+ }
551
+ function getDistanceBetweenPoints(pointA, pointB) {
552
+ return Math.sqrt(Math.pow(pointA.y - pointB.y, 2) + Math.pow(pointA.x - pointB.x, 2));
553
+ }
554
+ function getRotationBetweenPoints(pointA, pointB) {
555
+ return Math.atan2(pointB.y - pointA.y, pointB.x - pointA.x) * 180 / Math.PI;
556
+ }
557
+ /**
558
+ * Compute the output cropped area of the media in percentages and pixels.
559
+ * x/y are the top-left coordinates on the src media
560
+ */
561
+ function computeCroppedArea(crop, mediaSize, cropSize, aspect, zoom, rotation, restrictPosition) {
562
+ if (rotation === void 0) rotation = 0;
563
+ if (restrictPosition === void 0) restrictPosition = true;
564
+ var limitAreaFn = restrictPosition ? limitArea : noOp;
565
+ var mediaBBoxSize = rotateSize(mediaSize.width, mediaSize.height, rotation);
566
+ var mediaNaturalBBoxSize = rotateSize(mediaSize.naturalWidth, mediaSize.naturalHeight, rotation);
567
+ var croppedAreaPercentages = {
568
+ x: limitAreaFn(100, ((mediaBBoxSize.width - cropSize.width / zoom) / 2 - crop.x / zoom) / mediaBBoxSize.width * 100),
569
+ y: limitAreaFn(100, ((mediaBBoxSize.height - cropSize.height / zoom) / 2 - crop.y / zoom) / mediaBBoxSize.height * 100),
570
+ width: limitAreaFn(100, cropSize.width / mediaBBoxSize.width * 100 / zoom),
571
+ height: limitAreaFn(100, cropSize.height / mediaBBoxSize.height * 100 / zoom)
572
+ };
573
+ var widthInPixels = Math.round(limitAreaFn(mediaNaturalBBoxSize.width, croppedAreaPercentages.width * mediaNaturalBBoxSize.width / 100));
574
+ var heightInPixels = Math.round(limitAreaFn(mediaNaturalBBoxSize.height, croppedAreaPercentages.height * mediaNaturalBBoxSize.height / 100));
575
+ var sizePixels = mediaNaturalBBoxSize.width >= mediaNaturalBBoxSize.height * aspect ? {
576
+ width: Math.round(heightInPixels * aspect),
577
+ height: heightInPixels
578
+ } : {
579
+ width: widthInPixels,
580
+ height: Math.round(widthInPixels / aspect)
581
+ };
582
+ return {
583
+ croppedAreaPercentages,
584
+ croppedAreaPixels: __assign(__assign({}, sizePixels), {
585
+ x: Math.round(limitAreaFn(mediaNaturalBBoxSize.width - sizePixels.width, croppedAreaPercentages.x * mediaNaturalBBoxSize.width / 100)),
586
+ y: Math.round(limitAreaFn(mediaNaturalBBoxSize.height - sizePixels.height, croppedAreaPercentages.y * mediaNaturalBBoxSize.height / 100))
587
+ })
588
+ };
589
+ }
590
+ /**
591
+ * Ensure the returned value is between 0 and max
592
+ */
593
+ function limitArea(max, value) {
594
+ return Math.min(max, Math.max(0, value));
595
+ }
596
+ function noOp(_max, value) {
597
+ return value;
598
+ }
599
+ /**
600
+ * Compute crop and zoom from the croppedAreaPercentages.
601
+ */
602
+ function getInitialCropFromCroppedAreaPercentages(croppedAreaPercentages, mediaSize, rotation, cropSize, minZoom, maxZoom) {
603
+ var mediaBBoxSize = rotateSize(mediaSize.width, mediaSize.height, rotation);
604
+ var zoom = clamp(cropSize.width / mediaBBoxSize.width * (100 / croppedAreaPercentages.width), minZoom, maxZoom);
605
+ return {
606
+ crop: {
607
+ x: zoom * mediaBBoxSize.width / 2 - cropSize.width / 2 - mediaBBoxSize.width * zoom * (croppedAreaPercentages.x / 100),
608
+ y: zoom * mediaBBoxSize.height / 2 - cropSize.height / 2 - mediaBBoxSize.height * zoom * (croppedAreaPercentages.y / 100)
609
+ },
610
+ zoom
611
+ };
612
+ }
613
+ /**
614
+ * Compute zoom from the croppedAreaPixels
615
+ */
616
+ function getZoomFromCroppedAreaPixels(croppedAreaPixels, mediaSize, cropSize) {
617
+ var mediaZoom = getMediaZoom(mediaSize);
618
+ return cropSize.height > cropSize.width ? cropSize.height / (croppedAreaPixels.height * mediaZoom) : cropSize.width / (croppedAreaPixels.width * mediaZoom);
619
+ }
620
+ /**
621
+ * Compute crop and zoom from the croppedAreaPixels
622
+ */
623
+ function getInitialCropFromCroppedAreaPixels(croppedAreaPixels, mediaSize, rotation, cropSize, minZoom, maxZoom) {
624
+ if (rotation === void 0) rotation = 0;
625
+ var mediaNaturalBBoxSize = rotateSize(mediaSize.naturalWidth, mediaSize.naturalHeight, rotation);
626
+ var zoom = clamp(getZoomFromCroppedAreaPixels(croppedAreaPixels, mediaSize, cropSize), minZoom, maxZoom);
627
+ var cropZoom = cropSize.height > cropSize.width ? cropSize.height / croppedAreaPixels.height : cropSize.width / croppedAreaPixels.width;
628
+ return {
629
+ crop: {
630
+ x: ((mediaNaturalBBoxSize.width - croppedAreaPixels.width) / 2 - croppedAreaPixels.x) * cropZoom,
631
+ y: ((mediaNaturalBBoxSize.height - croppedAreaPixels.height) / 2 - croppedAreaPixels.y) * cropZoom
632
+ },
633
+ zoom
634
+ };
635
+ }
636
+ /**
637
+ * Return the point that is the center of point a and b
638
+ */
639
+ function getCenter(a, b) {
640
+ return {
641
+ x: (b.x + a.x) / 2,
642
+ y: (b.y + a.y) / 2
643
+ };
644
+ }
645
+ function getRadianAngle(degreeValue) {
646
+ return degreeValue * Math.PI / 180;
647
+ }
648
+ /**
649
+ * Returns the new bounding area of a rotated rectangle.
650
+ */
651
+ function rotateSize(width, height, rotation) {
652
+ var rotRad = getRadianAngle(rotation);
653
+ return {
654
+ width: Math.abs(Math.cos(rotRad) * width) + Math.abs(Math.sin(rotRad) * height),
655
+ height: Math.abs(Math.sin(rotRad) * width) + Math.abs(Math.cos(rotRad) * height)
656
+ };
657
+ }
658
+ /**
659
+ * Clamp value between min and max
660
+ */
661
+ function clamp(value, min, max) {
662
+ return Math.min(Math.max(value, min), max);
663
+ }
664
+ /**
665
+ * Combine multiple class names into a single string.
666
+ */
667
+ function classNames() {
668
+ var args = [];
669
+ for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
670
+ return args.filter(function(value) {
671
+ if (typeof value === "string" && value.length > 0) return true;
672
+ return false;
673
+ }).join(" ").trim();
674
+ }
675
+ var css_248z = ".reactEasyCrop_Container {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n overflow: hidden;\n user-select: none;\n touch-action: none;\n cursor: move;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n\n.reactEasyCrop_Image,\n.reactEasyCrop_Video {\n will-change: transform; /* this improves performances and prevent painting issues on iOS Chrome */\n}\n\n.reactEasyCrop_Contain {\n max-width: 100%;\n max-height: 100%;\n margin: auto;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n}\n.reactEasyCrop_Cover_Horizontal {\n width: 100%;\n height: auto;\n}\n.reactEasyCrop_Cover_Vertical {\n width: auto;\n height: 100%;\n}\n\n.reactEasyCrop_CropArea {\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n border: 1px solid rgba(255, 255, 255, 0.5);\n box-sizing: border-box;\n box-shadow: 0 0 0 9999em;\n color: rgba(0, 0, 0, 0.5);\n overflow: hidden;\n}\n\n.reactEasyCrop_CropAreaRound {\n border-radius: 50%;\n}\n\n.reactEasyCrop_CropAreaGrid::before {\n content: ' ';\n box-sizing: border-box;\n position: absolute;\n border: 1px solid rgba(255, 255, 255, 0.5);\n top: 0;\n bottom: 0;\n left: 33.33%;\n right: 33.33%;\n border-top: 0;\n border-bottom: 0;\n}\n\n.reactEasyCrop_CropAreaGrid::after {\n content: ' ';\n box-sizing: border-box;\n position: absolute;\n border: 1px solid rgba(255, 255, 255, 0.5);\n top: 33.33%;\n bottom: 33.33%;\n left: 0;\n right: 0;\n border-left: 0;\n border-right: 0;\n}\n";
676
+ var MIN_ZOOM = 1;
677
+ var MAX_ZOOM = 3;
678
+ var KEYBOARD_STEP = 1;
679
+ //#endregion
680
+ //#region src/client/avatar/portrait-editor.ts
681
+ const Cropper = function(_super) {
682
+ __extends(Cropper, _super);
683
+ function Cropper() {
684
+ var _this = _super !== null && _super.apply(this, arguments) || this;
685
+ _this.cropperRef = react.createRef();
686
+ _this.imageRef = react.createRef();
687
+ _this.videoRef = react.createRef();
688
+ _this.containerPosition = {
689
+ x: 0,
690
+ y: 0
691
+ };
692
+ _this.containerRef = null;
693
+ _this.styleRef = null;
694
+ _this.containerRect = null;
695
+ _this.mediaSize = {
696
+ width: 0,
697
+ height: 0,
698
+ naturalWidth: 0,
699
+ naturalHeight: 0
700
+ };
701
+ _this.dragStartPosition = {
702
+ x: 0,
703
+ y: 0
704
+ };
705
+ _this.dragStartCrop = {
706
+ x: 0,
707
+ y: 0
708
+ };
709
+ _this.gestureZoomStart = 0;
710
+ _this.gestureRotationStart = 0;
711
+ _this.isTouching = false;
712
+ _this.lastPinchDistance = 0;
713
+ _this.lastPinchRotation = 0;
714
+ _this.rafDragTimeout = null;
715
+ _this.rafPinchTimeout = null;
716
+ _this.wheelTimer = null;
717
+ _this.currentDoc = typeof document !== "undefined" ? document : null;
718
+ _this.currentWindow = typeof window !== "undefined" ? window : null;
719
+ _this.resizeObserver = null;
720
+ _this.previousCropSize = null;
721
+ _this.isInitialized = false;
722
+ _this.state = {
723
+ cropSize: null,
724
+ hasWheelJustStarted: false,
725
+ mediaObjectFit: void 0
726
+ };
727
+ _this.initResizeObserver = function() {
728
+ if (typeof window.ResizeObserver === "undefined" || !_this.containerRef) return;
729
+ var isFirstResize = true;
730
+ _this.resizeObserver = new window.ResizeObserver(function(entries) {
731
+ if (isFirstResize) {
732
+ isFirstResize = false;
733
+ return;
734
+ }
735
+ _this.computeSizes();
736
+ });
737
+ _this.resizeObserver.observe(_this.containerRef);
738
+ };
739
+ _this.preventZoomSafari = function(e) {
740
+ return e.preventDefault();
741
+ };
742
+ _this.cleanEvents = function() {
743
+ if (!_this.currentDoc) return;
744
+ _this.currentDoc.removeEventListener("mousemove", _this.onMouseMove);
745
+ _this.currentDoc.removeEventListener("mouseup", _this.onDragStopped);
746
+ _this.currentDoc.removeEventListener("touchmove", _this.onTouchMove);
747
+ _this.currentDoc.removeEventListener("touchend", _this.onDragStopped);
748
+ _this.currentDoc.removeEventListener("gesturechange", _this.onGestureChange);
749
+ _this.currentDoc.removeEventListener("gestureend", _this.onGestureEnd);
750
+ _this.currentDoc.removeEventListener("scroll", _this.onScroll);
751
+ };
752
+ _this.clearScrollEvent = function() {
753
+ if (_this.containerRef) _this.containerRef.removeEventListener("wheel", _this.onWheel);
754
+ if (_this.wheelTimer) clearTimeout(_this.wheelTimer);
755
+ };
756
+ _this.onMediaLoad = function() {
757
+ var cropSize = _this.computeSizes();
758
+ if (cropSize) {
759
+ _this.previousCropSize = cropSize;
760
+ _this.emitCropData();
761
+ _this.setInitialCrop(cropSize);
762
+ _this.isInitialized = true;
763
+ }
764
+ if (_this.props.onMediaLoaded) _this.props.onMediaLoaded(_this.mediaSize);
765
+ };
766
+ _this.setInitialCrop = function(cropSize) {
767
+ if (_this.props.initialCroppedAreaPercentages) {
768
+ var _a = getInitialCropFromCroppedAreaPercentages(_this.props.initialCroppedAreaPercentages, _this.mediaSize, _this.props.rotation, cropSize, _this.props.minZoom, _this.props.maxZoom), crop = _a.crop, zoom = _a.zoom;
769
+ _this.props.onCropChange(crop);
770
+ _this.props.onZoomChange && _this.props.onZoomChange(zoom);
771
+ } else if (_this.props.initialCroppedAreaPixels) {
772
+ var _b = getInitialCropFromCroppedAreaPixels(_this.props.initialCroppedAreaPixels, _this.mediaSize, _this.props.rotation, cropSize, _this.props.minZoom, _this.props.maxZoom), crop = _b.crop, zoom = _b.zoom;
773
+ _this.props.onCropChange(crop);
774
+ _this.props.onZoomChange && _this.props.onZoomChange(zoom);
775
+ }
776
+ };
777
+ _this.computeSizes = function() {
778
+ var _a, _b, _c, _d, _e, _f;
779
+ var mediaRef = _this.imageRef.current || _this.videoRef.current;
780
+ if (mediaRef && _this.containerRef) {
781
+ _this.containerRect = _this.containerRef.getBoundingClientRect();
782
+ _this.saveContainerPosition();
783
+ var containerAspect = _this.containerRect.width / _this.containerRect.height;
784
+ var naturalWidth = ((_a = _this.imageRef.current) === null || _a === void 0 ? void 0 : _a.naturalWidth) || ((_b = _this.videoRef.current) === null || _b === void 0 ? void 0 : _b.videoWidth) || 0;
785
+ var naturalHeight = ((_c = _this.imageRef.current) === null || _c === void 0 ? void 0 : _c.naturalHeight) || ((_d = _this.videoRef.current) === null || _d === void 0 ? void 0 : _d.videoHeight) || 0;
786
+ var isMediaScaledDown = mediaRef.offsetWidth < naturalWidth || mediaRef.offsetHeight < naturalHeight;
787
+ var mediaAspect = naturalWidth / naturalHeight;
788
+ var renderedMediaSize = void 0;
789
+ if (isMediaScaledDown) switch (_this.state.mediaObjectFit) {
790
+ default:
791
+ case "contain":
792
+ renderedMediaSize = containerAspect > mediaAspect ? {
793
+ width: _this.containerRect.height * mediaAspect,
794
+ height: _this.containerRect.height
795
+ } : {
796
+ width: _this.containerRect.width,
797
+ height: _this.containerRect.width / mediaAspect
798
+ };
799
+ break;
800
+ case "horizontal-cover":
801
+ renderedMediaSize = {
802
+ width: _this.containerRect.width,
803
+ height: _this.containerRect.width / mediaAspect
804
+ };
805
+ break;
806
+ case "vertical-cover": renderedMediaSize = {
807
+ width: _this.containerRect.height * mediaAspect,
808
+ height: _this.containerRect.height
809
+ };
810
+ }
811
+ else renderedMediaSize = {
812
+ width: mediaRef.offsetWidth,
813
+ height: mediaRef.offsetHeight
814
+ };
815
+ _this.mediaSize = __assign(__assign({}, renderedMediaSize), {
816
+ naturalWidth,
817
+ naturalHeight
818
+ });
819
+ if (_this.props.setMediaSize) _this.props.setMediaSize(_this.mediaSize);
820
+ var cropSize = _this.props.cropSize ? _this.props.cropSize : getCropSize(_this.mediaSize.width, _this.mediaSize.height, _this.containerRect.width, _this.containerRect.height, _this.props.aspect, _this.props.rotation);
821
+ if (((_e = _this.state.cropSize) === null || _e === void 0 ? void 0 : _e.height) !== cropSize.height || ((_f = _this.state.cropSize) === null || _f === void 0 ? void 0 : _f.width) !== cropSize.width) _this.props.onCropSizeChange && _this.props.onCropSizeChange(cropSize);
822
+ _this.setState({ cropSize }, _this.recomputeCropPosition);
823
+ if (_this.props.setCropSize) _this.props.setCropSize(cropSize);
824
+ return cropSize;
825
+ }
826
+ };
827
+ _this.saveContainerPosition = function() {
828
+ if (_this.containerRef) {
829
+ var bounds = _this.containerRef.getBoundingClientRect();
830
+ _this.containerPosition = {
831
+ x: bounds.left,
832
+ y: bounds.top
833
+ };
834
+ }
835
+ };
836
+ _this.onMouseDown = function(e) {
837
+ if (!_this.currentDoc) return;
838
+ e.preventDefault();
839
+ _this.currentDoc.addEventListener("mousemove", _this.onMouseMove);
840
+ _this.currentDoc.addEventListener("mouseup", _this.onDragStopped);
841
+ _this.saveContainerPosition();
842
+ _this.onDragStart(Cropper.getMousePoint(e));
843
+ };
844
+ _this.onMouseMove = function(e) {
845
+ return _this.onDrag(Cropper.getMousePoint(e));
846
+ };
847
+ _this.onScroll = function(e) {
848
+ if (!_this.currentDoc) return;
849
+ e.preventDefault();
850
+ _this.saveContainerPosition();
851
+ };
852
+ _this.onTouchStart = function(e) {
853
+ if (!_this.currentDoc) return;
854
+ _this.isTouching = true;
855
+ if (_this.props.onTouchRequest && !_this.props.onTouchRequest(e)) return;
856
+ _this.currentDoc.addEventListener("touchmove", _this.onTouchMove, { passive: false });
857
+ _this.currentDoc.addEventListener("touchend", _this.onDragStopped);
858
+ _this.saveContainerPosition();
859
+ if (e.touches.length === 2) _this.onPinchStart(e);
860
+ else if (e.touches.length === 1) _this.onDragStart(Cropper.getTouchPoint(e.touches[0]));
861
+ };
862
+ _this.onTouchMove = function(e) {
863
+ e.preventDefault();
864
+ if (e.touches.length === 2) _this.onPinchMove(e);
865
+ else if (e.touches.length === 1) _this.onDrag(Cropper.getTouchPoint(e.touches[0]));
866
+ };
867
+ _this.onGestureStart = function(e) {
868
+ if (!_this.currentDoc) return;
869
+ e.preventDefault();
870
+ _this.currentDoc.addEventListener("gesturechange", _this.onGestureChange);
871
+ _this.currentDoc.addEventListener("gestureend", _this.onGestureEnd);
872
+ _this.gestureZoomStart = _this.props.zoom;
873
+ _this.gestureRotationStart = _this.props.rotation;
874
+ };
875
+ _this.onGestureChange = function(e) {
876
+ e.preventDefault();
877
+ if (_this.isTouching) return;
878
+ var point = Cropper.getMousePoint(e);
879
+ var newZoom = _this.gestureZoomStart - 1 + e.scale;
880
+ _this.setNewZoom(newZoom, point, { shouldUpdatePosition: true });
881
+ if (_this.props.onRotationChange) {
882
+ var newRotation = _this.gestureRotationStart + e.rotation;
883
+ _this.props.onRotationChange(newRotation);
884
+ }
885
+ };
886
+ _this.onGestureEnd = function(e) {
887
+ _this.cleanEvents();
888
+ };
889
+ _this.onDragStart = function(_a) {
890
+ var _b, _c;
891
+ _this.dragStartPosition = {
892
+ x: _a.x,
893
+ y: _a.y
894
+ };
895
+ _this.dragStartCrop = __assign({}, _this.props.crop);
896
+ (_c = (_b = _this.props).onInteractionStart) === null || _c === void 0 || _c.call(_b);
897
+ };
898
+ _this.onDrag = function(_a) {
899
+ var x = _a.x, y = _a.y;
900
+ if (!_this.currentWindow) return;
901
+ if (_this.rafDragTimeout) _this.currentWindow.cancelAnimationFrame(_this.rafDragTimeout);
902
+ _this.rafDragTimeout = _this.currentWindow.requestAnimationFrame(function() {
903
+ if (!_this.state.cropSize) return;
904
+ if (x === void 0 || y === void 0) return;
905
+ var offsetX = x - _this.dragStartPosition.x;
906
+ var offsetY = y - _this.dragStartPosition.y;
907
+ var requestedPosition = {
908
+ x: _this.dragStartCrop.x + offsetX,
909
+ y: _this.dragStartCrop.y + offsetY
910
+ };
911
+ var newPosition = _this.props.restrictPosition ? restrictPosition(requestedPosition, _this.mediaSize, _this.state.cropSize, _this.props.zoom, _this.props.rotation) : requestedPosition;
912
+ _this.props.onCropChange(newPosition);
913
+ });
914
+ };
915
+ _this.onDragStopped = function() {
916
+ var _a, _b;
917
+ _this.isTouching = false;
918
+ _this.cleanEvents();
919
+ _this.emitCropData();
920
+ (_b = (_a = _this.props).onInteractionEnd) === null || _b === void 0 || _b.call(_a);
921
+ };
922
+ _this.onWheel = function(e) {
923
+ if (!_this.currentWindow) return;
924
+ if (_this.props.onWheelRequest && !_this.props.onWheelRequest(e)) return;
925
+ e.preventDefault();
926
+ var point = Cropper.getMousePoint(e);
927
+ var pixelY = (0, import_normalize_wheel.default)(e).pixelY;
928
+ var newZoom = _this.props.zoom - pixelY * _this.props.zoomSpeed / 200;
929
+ _this.setNewZoom(newZoom, point, { shouldUpdatePosition: true });
930
+ if (!_this.state.hasWheelJustStarted) _this.setState({ hasWheelJustStarted: true }, function() {
931
+ var _a, _b;
932
+ return (_b = (_a = _this.props).onInteractionStart) === null || _b === void 0 ? void 0 : _b.call(_a);
933
+ });
934
+ if (_this.wheelTimer) clearTimeout(_this.wheelTimer);
935
+ _this.wheelTimer = _this.currentWindow.setTimeout(function() {
936
+ return _this.setState({ hasWheelJustStarted: false }, function() {
937
+ var _a, _b;
938
+ return (_b = (_a = _this.props).onInteractionEnd) === null || _b === void 0 ? void 0 : _b.call(_a);
939
+ });
940
+ }, 250);
941
+ };
942
+ _this.getPointOnContainer = function(_a, containerTopLeft) {
943
+ var x = _a.x, y = _a.y;
944
+ if (!_this.containerRect) throw new Error("The Cropper is not mounted");
945
+ return {
946
+ x: _this.containerRect.width / 2 - (x - containerTopLeft.x),
947
+ y: _this.containerRect.height / 2 - (y - containerTopLeft.y)
948
+ };
949
+ };
950
+ _this.getPointOnMedia = function(_a) {
951
+ var x = _a.x, y = _a.y;
952
+ var _b = _this.props, crop = _b.crop, zoom = _b.zoom;
953
+ return {
954
+ x: (x + crop.x) / zoom,
955
+ y: (y + crop.y) / zoom
956
+ };
957
+ };
958
+ _this.setNewZoom = function(zoom, point, _a) {
959
+ var _c = (_a === void 0 ? {} : _a).shouldUpdatePosition, shouldUpdatePosition = _c === void 0 ? true : _c;
960
+ if (!_this.state.cropSize || !_this.props.onZoomChange) return;
961
+ var newZoom = clamp(zoom, _this.props.minZoom, _this.props.maxZoom);
962
+ if (shouldUpdatePosition) {
963
+ var zoomPoint = _this.getPointOnContainer(point, _this.containerPosition);
964
+ var zoomTarget = _this.getPointOnMedia(zoomPoint);
965
+ var requestedPosition = {
966
+ x: zoomTarget.x * newZoom - zoomPoint.x,
967
+ y: zoomTarget.y * newZoom - zoomPoint.y
968
+ };
969
+ var newPosition = _this.props.restrictPosition ? restrictPosition(requestedPosition, _this.mediaSize, _this.state.cropSize, newZoom, _this.props.rotation) : requestedPosition;
970
+ _this.props.onCropChange(newPosition);
971
+ }
972
+ _this.props.onZoomChange(newZoom);
973
+ };
974
+ _this.getCropData = function() {
975
+ if (!_this.state.cropSize) return null;
976
+ return computeCroppedArea(_this.props.restrictPosition ? restrictPosition(_this.props.crop, _this.mediaSize, _this.state.cropSize, _this.props.zoom, _this.props.rotation) : _this.props.crop, _this.mediaSize, _this.state.cropSize, _this.getAspect(), _this.props.zoom, _this.props.rotation, _this.props.restrictPosition);
977
+ };
978
+ _this.emitCropData = function() {
979
+ var cropData = _this.getCropData();
980
+ if (!cropData) return;
981
+ var croppedAreaPercentages = cropData.croppedAreaPercentages, croppedAreaPixels = cropData.croppedAreaPixels;
982
+ if (_this.props.onCropComplete) _this.props.onCropComplete(croppedAreaPercentages, croppedAreaPixels);
983
+ if (_this.props.onCropAreaChange) _this.props.onCropAreaChange(croppedAreaPercentages, croppedAreaPixels);
984
+ };
985
+ _this.emitCropAreaChange = function() {
986
+ var cropData = _this.getCropData();
987
+ if (!cropData) return;
988
+ var croppedAreaPercentages = cropData.croppedAreaPercentages, croppedAreaPixels = cropData.croppedAreaPixels;
989
+ if (_this.props.onCropAreaChange) _this.props.onCropAreaChange(croppedAreaPercentages, croppedAreaPixels);
990
+ };
991
+ _this.recomputeCropPosition = function() {
992
+ var _a, _b;
993
+ if (!_this.state.cropSize) return;
994
+ var adjustedCrop = _this.props.crop;
995
+ if (_this.isInitialized && ((_a = _this.previousCropSize) === null || _a === void 0 ? void 0 : _a.width) && ((_b = _this.previousCropSize) === null || _b === void 0 ? void 0 : _b.height)) {
996
+ if (Math.abs(_this.previousCropSize.width - _this.state.cropSize.width) > 1e-6 || Math.abs(_this.previousCropSize.height - _this.state.cropSize.height) > 1e-6) {
997
+ var scaleX = _this.state.cropSize.width / _this.previousCropSize.width;
998
+ var scaleY = _this.state.cropSize.height / _this.previousCropSize.height;
999
+ adjustedCrop = {
1000
+ x: _this.props.crop.x * scaleX,
1001
+ y: _this.props.crop.y * scaleY
1002
+ };
1003
+ }
1004
+ }
1005
+ var newPosition = _this.props.restrictPosition ? restrictPosition(adjustedCrop, _this.mediaSize, _this.state.cropSize, _this.props.zoom, _this.props.rotation) : adjustedCrop;
1006
+ _this.previousCropSize = _this.state.cropSize;
1007
+ _this.props.onCropChange(newPosition);
1008
+ _this.emitCropData();
1009
+ };
1010
+ _this.onKeyDown = function(event) {
1011
+ var _a, _b;
1012
+ var _c = _this.props, crop = _c.crop, onCropChange = _c.onCropChange, keyboardStep = _c.keyboardStep, zoom = _c.zoom, rotation = _c.rotation;
1013
+ var step = keyboardStep;
1014
+ if (!_this.state.cropSize) return;
1015
+ if (event.shiftKey) step *= .2;
1016
+ var newCrop = __assign({}, crop);
1017
+ switch (event.key) {
1018
+ case "ArrowUp":
1019
+ newCrop.y -= step;
1020
+ event.preventDefault();
1021
+ break;
1022
+ case "ArrowDown":
1023
+ newCrop.y += step;
1024
+ event.preventDefault();
1025
+ break;
1026
+ case "ArrowLeft":
1027
+ newCrop.x -= step;
1028
+ event.preventDefault();
1029
+ break;
1030
+ case "ArrowRight":
1031
+ newCrop.x += step;
1032
+ event.preventDefault();
1033
+ break;
1034
+ default: return;
1035
+ }
1036
+ if (_this.props.restrictPosition) newCrop = restrictPosition(newCrop, _this.mediaSize, _this.state.cropSize, zoom, rotation);
1037
+ if (!event.repeat) (_b = (_a = _this.props).onInteractionStart) === null || _b === void 0 || _b.call(_a);
1038
+ onCropChange(newCrop);
1039
+ };
1040
+ _this.onKeyUp = function(event) {
1041
+ var _a, _b;
1042
+ switch (event.key) {
1043
+ case "ArrowUp":
1044
+ case "ArrowDown":
1045
+ case "ArrowLeft":
1046
+ case "ArrowRight":
1047
+ event.preventDefault();
1048
+ break;
1049
+ default: return;
1050
+ }
1051
+ _this.emitCropData();
1052
+ (_b = (_a = _this.props).onInteractionEnd) === null || _b === void 0 || _b.call(_a);
1053
+ };
1054
+ return _this;
1055
+ }
1056
+ Cropper.prototype.componentDidMount = function() {
1057
+ if (!this.currentDoc || !this.currentWindow) return;
1058
+ if (this.containerRef) {
1059
+ if (this.containerRef.ownerDocument) this.currentDoc = this.containerRef.ownerDocument;
1060
+ if (this.currentDoc.defaultView) this.currentWindow = this.currentDoc.defaultView;
1061
+ this.initResizeObserver();
1062
+ if (typeof window.ResizeObserver === "undefined") this.currentWindow.addEventListener("resize", this.computeSizes);
1063
+ this.props.zoomWithScroll && this.containerRef.addEventListener("wheel", this.onWheel, { passive: false });
1064
+ this.containerRef.addEventListener("gesturestart", this.onGestureStart);
1065
+ }
1066
+ this.currentDoc.addEventListener("scroll", this.onScroll);
1067
+ if (!this.props.disableAutomaticStylesInjection) {
1068
+ this.styleRef = this.currentDoc.createElement("style");
1069
+ this.styleRef.setAttribute("type", "text/css");
1070
+ if (this.props.nonce) this.styleRef.setAttribute("nonce", this.props.nonce);
1071
+ this.styleRef.innerHTML = css_248z;
1072
+ this.currentDoc.head.appendChild(this.styleRef);
1073
+ }
1074
+ if (this.imageRef.current && this.imageRef.current.complete) this.onMediaLoad();
1075
+ if (this.props.setImageRef) this.props.setImageRef(this.imageRef);
1076
+ if (this.props.setVideoRef) this.props.setVideoRef(this.videoRef);
1077
+ if (this.props.setCropperRef) this.props.setCropperRef(this.cropperRef);
1078
+ };
1079
+ Cropper.prototype.componentWillUnmount = function() {
1080
+ var _a, _b;
1081
+ if (!this.currentDoc || !this.currentWindow) return;
1082
+ if (typeof window.ResizeObserver === "undefined") this.currentWindow.removeEventListener("resize", this.computeSizes);
1083
+ (_a = this.resizeObserver) === null || _a === void 0 || _a.disconnect();
1084
+ if (this.containerRef) this.containerRef.removeEventListener("gesturestart", this.preventZoomSafari);
1085
+ if (this.styleRef) (_b = this.styleRef.parentNode) === null || _b === void 0 || _b.removeChild(this.styleRef);
1086
+ this.cleanEvents();
1087
+ this.props.zoomWithScroll && this.clearScrollEvent();
1088
+ };
1089
+ Cropper.prototype.componentDidUpdate = function(prevProps) {
1090
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1091
+ if (prevProps.rotation !== this.props.rotation) {
1092
+ this.computeSizes();
1093
+ this.recomputeCropPosition();
1094
+ } else if (prevProps.aspect !== this.props.aspect) this.computeSizes();
1095
+ else if (prevProps.objectFit !== this.props.objectFit) this.computeSizes();
1096
+ else if (prevProps.zoom !== this.props.zoom) this.recomputeCropPosition();
1097
+ else if (((_a = prevProps.cropSize) === null || _a === void 0 ? void 0 : _a.height) !== ((_b = this.props.cropSize) === null || _b === void 0 ? void 0 : _b.height) || ((_c = prevProps.cropSize) === null || _c === void 0 ? void 0 : _c.width) !== ((_d = this.props.cropSize) === null || _d === void 0 ? void 0 : _d.width)) this.computeSizes();
1098
+ else if (((_e = prevProps.crop) === null || _e === void 0 ? void 0 : _e.x) !== ((_f = this.props.crop) === null || _f === void 0 ? void 0 : _f.x) || ((_g = prevProps.crop) === null || _g === void 0 ? void 0 : _g.y) !== ((_h = this.props.crop) === null || _h === void 0 ? void 0 : _h.y)) this.emitCropAreaChange();
1099
+ if (prevProps.zoomWithScroll !== this.props.zoomWithScroll && this.containerRef) this.props.zoomWithScroll ? this.containerRef.addEventListener("wheel", this.onWheel, { passive: false }) : this.clearScrollEvent();
1100
+ if (prevProps.video !== this.props.video) (_j = this.videoRef.current) === null || _j === void 0 || _j.load();
1101
+ var objectFit = this.getObjectFit();
1102
+ if (objectFit !== this.state.mediaObjectFit) this.setState({ mediaObjectFit: objectFit }, this.computeSizes);
1103
+ };
1104
+ Cropper.prototype.getAspect = function() {
1105
+ var _a = this.props, cropSize = _a.cropSize, aspect = _a.aspect;
1106
+ if (cropSize) return cropSize.width / cropSize.height;
1107
+ return aspect;
1108
+ };
1109
+ Cropper.prototype.getObjectFit = function() {
1110
+ var _a, _b, _c, _d;
1111
+ if (this.props.objectFit === "cover") {
1112
+ if ((this.imageRef.current || this.videoRef.current) && this.containerRef) {
1113
+ this.containerRect = this.containerRef.getBoundingClientRect();
1114
+ var containerAspect = this.containerRect.width / this.containerRect.height;
1115
+ return (((_a = this.imageRef.current) === null || _a === void 0 ? void 0 : _a.naturalWidth) || ((_b = this.videoRef.current) === null || _b === void 0 ? void 0 : _b.videoWidth) || 0) / (((_c = this.imageRef.current) === null || _c === void 0 ? void 0 : _c.naturalHeight) || ((_d = this.videoRef.current) === null || _d === void 0 ? void 0 : _d.videoHeight) || 0) < containerAspect ? "horizontal-cover" : "vertical-cover";
1116
+ }
1117
+ return "horizontal-cover";
1118
+ }
1119
+ return this.props.objectFit;
1120
+ };
1121
+ Cropper.prototype.onPinchStart = function(e) {
1122
+ var pointA = Cropper.getTouchPoint(e.touches[0]);
1123
+ var pointB = Cropper.getTouchPoint(e.touches[1]);
1124
+ this.lastPinchDistance = getDistanceBetweenPoints(pointA, pointB);
1125
+ this.lastPinchRotation = getRotationBetweenPoints(pointA, pointB);
1126
+ this.onDragStart(getCenter(pointA, pointB));
1127
+ };
1128
+ Cropper.prototype.onPinchMove = function(e) {
1129
+ var _this = this;
1130
+ if (!this.currentDoc || !this.currentWindow) return;
1131
+ var pointA = Cropper.getTouchPoint(e.touches[0]);
1132
+ var pointB = Cropper.getTouchPoint(e.touches[1]);
1133
+ var center = getCenter(pointA, pointB);
1134
+ this.onDrag(center);
1135
+ if (this.rafPinchTimeout) this.currentWindow.cancelAnimationFrame(this.rafPinchTimeout);
1136
+ this.rafPinchTimeout = this.currentWindow.requestAnimationFrame(function() {
1137
+ var distance = getDistanceBetweenPoints(pointA, pointB);
1138
+ var newZoom = _this.props.zoom * (distance / _this.lastPinchDistance);
1139
+ _this.setNewZoom(newZoom, center, { shouldUpdatePosition: false });
1140
+ _this.lastPinchDistance = distance;
1141
+ var rotation = getRotationBetweenPoints(pointA, pointB);
1142
+ var newRotation = _this.props.rotation + (rotation - _this.lastPinchRotation);
1143
+ _this.props.onRotationChange && _this.props.onRotationChange(newRotation);
1144
+ _this.lastPinchRotation = rotation;
1145
+ });
1146
+ };
1147
+ Cropper.prototype.render = function() {
1148
+ var _this = this;
1149
+ var _a;
1150
+ var _b = this.props, image = _b.image, video = _b.video, mediaProps = _b.mediaProps, cropperProps = _b.cropperProps, transform = _b.transform, _c = _b.crop, x = _c.x, y = _c.y, rotation = _b.rotation, zoom = _b.zoom, cropShape = _b.cropShape, showGrid = _b.showGrid, roundCropAreaPixels = _b.roundCropAreaPixels, _d = _b.style, containerStyle = _d.containerStyle, cropAreaStyle = _d.cropAreaStyle, mediaStyle = _d.mediaStyle, _e = _b.classes, containerClassName = _e.containerClassName, cropAreaClassName = _e.cropAreaClassName, mediaClassName = _e.mediaClassName;
1151
+ var objectFit = (_a = this.state.mediaObjectFit) !== null && _a !== void 0 ? _a : this.getObjectFit();
1152
+ return react.createElement("div", {
1153
+ onMouseDown: this.onMouseDown,
1154
+ onTouchStart: this.onTouchStart,
1155
+ ref: function ref(el) {
1156
+ return _this.containerRef = el;
1157
+ },
1158
+ "data-testid": "container",
1159
+ style: containerStyle,
1160
+ className: classNames("reactEasyCrop_Container", containerClassName)
1161
+ }, image ? react.createElement("img", __assign({
1162
+ alt: "",
1163
+ className: classNames("reactEasyCrop_Image", objectFit === "contain" && "reactEasyCrop_Contain", objectFit === "horizontal-cover" && "reactEasyCrop_Cover_Horizontal", objectFit === "vertical-cover" && "reactEasyCrop_Cover_Vertical", mediaClassName)
1164
+ }, mediaProps, {
1165
+ src: image,
1166
+ ref: this.imageRef,
1167
+ style: __assign(__assign({}, mediaStyle), { transform: transform || "translate(".concat(x, "px, ").concat(y, "px) rotate(").concat(rotation, "deg) scale(").concat(zoom, ")") }),
1168
+ onLoad: this.onMediaLoad
1169
+ })) : video && react.createElement("video", __assign({
1170
+ autoPlay: true,
1171
+ playsInline: true,
1172
+ loop: true,
1173
+ muted: true,
1174
+ className: classNames("reactEasyCrop_Video", objectFit === "contain" && "reactEasyCrop_Contain", objectFit === "horizontal-cover" && "reactEasyCrop_Cover_Horizontal", objectFit === "vertical-cover" && "reactEasyCrop_Cover_Vertical", mediaClassName)
1175
+ }, mediaProps, {
1176
+ ref: this.videoRef,
1177
+ onLoadedMetadata: this.onMediaLoad,
1178
+ style: __assign(__assign({}, mediaStyle), { transform: transform || "translate(".concat(x, "px, ").concat(y, "px) rotate(").concat(rotation, "deg) scale(").concat(zoom, ")") }),
1179
+ controls: false
1180
+ }), (Array.isArray(video) ? video : [{ src: video }]).map(function(item) {
1181
+ return react.createElement("source", __assign({ key: item.src }, item));
1182
+ })), this.state.cropSize && react.createElement("div", __assign({
1183
+ ref: this.cropperRef,
1184
+ style: __assign(__assign({}, cropAreaStyle), {
1185
+ width: roundCropAreaPixels ? Math.round(this.state.cropSize.width) : this.state.cropSize.width,
1186
+ height: roundCropAreaPixels ? Math.round(this.state.cropSize.height) : this.state.cropSize.height
1187
+ }),
1188
+ tabIndex: 0,
1189
+ onKeyDown: this.onKeyDown,
1190
+ onKeyUp: this.onKeyUp,
1191
+ "data-testid": "cropper",
1192
+ className: classNames("reactEasyCrop_CropArea", cropShape === "round" && "reactEasyCrop_CropAreaRound", showGrid && "reactEasyCrop_CropAreaGrid", cropAreaClassName)
1193
+ }, cropperProps)));
1194
+ };
1195
+ Cropper.defaultProps = {
1196
+ zoom: 1,
1197
+ rotation: 0,
1198
+ aspect: 4 / 3,
1199
+ maxZoom: MAX_ZOOM,
1200
+ minZoom: MIN_ZOOM,
1201
+ cropShape: "rect",
1202
+ objectFit: "contain",
1203
+ showGrid: true,
1204
+ style: {},
1205
+ classes: {},
1206
+ mediaProps: {},
1207
+ cropperProps: {},
1208
+ zoomSpeed: 1,
1209
+ restrictPosition: true,
1210
+ zoomWithScroll: true,
1211
+ keyboardStep: KEYBOARD_STEP
1212
+ };
1213
+ Cropper.getMousePoint = function(e) {
1214
+ return {
1215
+ x: Number(e.clientX),
1216
+ y: Number(e.clientY)
1217
+ };
1218
+ };
1219
+ Cropper.getTouchPoint = function(touch) {
1220
+ return {
1221
+ x: Number(touch.clientX),
1222
+ y: Number(touch.clientY)
1223
+ };
1224
+ };
1225
+ return Cropper;
1226
+ }(react.Component);
1227
+ /** 与名称同一行的头像设置;资源或渲染器变化时销毁未确认的裁剪会话。 */
1228
+ function AssistantAvatarPortraitEditor(props) {
1229
+ const registry = require_manager.getAssistantAvatarRegistry(props.services), id = require_manager.resolveAssistantAvatarAsset(props.model, "dialog").renderer;
1230
+ const read = (0, react.useCallback)(() => registry.get(id), [registry, id]);
1231
+ const renderer = (0, react.useSyncExternalStore)(registry.subscribe, read, read);
1232
+ return (0, react.createElement)(PortraitEditorContent, {
1233
+ ...props,
1234
+ renderer,
1235
+ key: require_manager.assistantAvatarPortraitKey(props.model, renderer)
1236
+ });
1237
+ }
1238
+ function PortraitEditorContent({ services, model, renderer, disabled, t }) {
1239
+ const service = require_manager.getAssistantAvatarPortraitService(services);
1240
+ const [open, setOpen] = (0, react.useState)(false), [pending, setPending] = (0, react.useState)(false), [error, setError] = (0, react.useState)("");
1241
+ const [source, setSource] = (0, react.useState)();
1242
+ const [area, setArea] = (0, react.useState)(), [crop, setCrop] = (0, react.useState)({
1243
+ x: 0,
1244
+ y: 0
1245
+ }), [zoom, setZoom] = (0, react.useState)(1);
1246
+ const [percentages, setPercentages] = (0, react.useState)();
1247
+ const session = (0, react.useRef)();
1248
+ const release = (0, react.useCallback)(() => {
1249
+ session.current?.controller.abort();
1250
+ if (session.current?.url !== void 0) URL.revokeObjectURL(session.current.url);
1251
+ session.current = void 0;
1252
+ }, []);
1253
+ const close = (0, react.useCallback)(() => {
1254
+ release();
1255
+ setOpen(false);
1256
+ setSource(void 0);
1257
+ setArea(void 0);
1258
+ setPercentages(void 0);
1259
+ setPending(false);
1260
+ setError("");
1261
+ }, [release]);
1262
+ (0, react.useEffect)(() => release, [release]);
1263
+ (0, react.useEffect)(() => {
1264
+ if (disabled) close();
1265
+ }, [disabled, close]);
1266
+ const edit = async () => {
1267
+ if (disabled || pending) return;
1268
+ close();
1269
+ setOpen(true);
1270
+ setPending(true);
1271
+ setCrop({
1272
+ x: 0,
1273
+ y: 0
1274
+ });
1275
+ setZoom(1);
1276
+ const active = { controller: new AbortController() };
1277
+ session.current = active;
1278
+ try {
1279
+ const preview = await service.getSource(model, renderer, active.controller.signal);
1280
+ if (preview === void 0) throw new Error("avatar_portrait_source_unavailable");
1281
+ const initial = preview.cropAreaPercentages ?? await require_manager.initialAssistantAvatarPortraitArea(preview, active.controller.signal);
1282
+ if (active.controller.signal.aborted || session.current !== active) return;
1283
+ active.url = URL.createObjectURL(preview.blob);
1284
+ setSource({
1285
+ preview,
1286
+ url: active.url,
1287
+ ...initial === void 0 ? {} : { initial }
1288
+ });
1289
+ } catch {
1290
+ if (!active.controller.signal.aborted) setError(t("avatar.portraitSourceFailed"));
1291
+ } finally {
1292
+ if (session.current === active) setPending(false);
1293
+ }
1294
+ };
1295
+ const save = async () => {
1296
+ const active = session.current;
1297
+ if (disabled || pending || source === void 0 || area === void 0 || active === void 0) return;
1298
+ setPending(true);
1299
+ setError("");
1300
+ try {
1301
+ const preview = await require_manager.cropAssistantAvatarPortrait(source.preview, area, active.controller.signal);
1302
+ if (preview === void 0) throw new Error("avatar_portrait_crop_failed");
1303
+ await service.setPortrait(model, renderer, {
1304
+ ...preview,
1305
+ ...percentages === void 0 ? {} : { cropAreaPercentages: percentages }
1306
+ }, active.controller.signal);
1307
+ if (session.current === active) close();
1308
+ } catch {
1309
+ if (!active.controller.signal.aborted) setError(t("avatar.portraitSaveFailed"));
1310
+ } finally {
1311
+ if (session.current === active) setPending(false);
1312
+ }
1313
+ };
1314
+ const changedArea = (0, react.useCallback)((percent, pixels) => {
1315
+ setArea(pixels);
1316
+ setPercentages(percent);
1317
+ }, []);
1318
+ const locked = disabled || pending && source !== void 0;
1319
+ return (0, react.createElement)("div", {
1320
+ "data-codingns-portrait-settings": model.id,
1321
+ style: {
1322
+ ...require_assistant_workbench.assistantSettingTextStyle,
1323
+ display: "contents"
1324
+ }
1325
+ }, (0, react.createElement)("div", {
1326
+ "data-codingns-portrait-controls": true,
1327
+ style: {
1328
+ display: "grid",
1329
+ gap: 6,
1330
+ minWidth: 0
1331
+ }
1332
+ }, (0, react.createElement)("span", null, t("avatar.portraitTitle")), (0, react.createElement)("div", { style: {
1333
+ display: "flex",
1334
+ alignItems: "center",
1335
+ flexWrap: "wrap",
1336
+ gap: 12
1337
+ } }, (0, react.createElement)(require_manager.AssistantAvatarPortrait, {
1338
+ services,
1339
+ model,
1340
+ size: 44
1341
+ }), (0, react.createElement)("button", {
1342
+ type: "button",
1343
+ disabled: disabled || pending,
1344
+ "aria-expanded": open,
1345
+ "data-codingns-portrait-edit": true,
1346
+ style: require_theme.dshSettingsButtonStyle,
1347
+ onClick: () => {
1348
+ edit();
1349
+ }
1350
+ }, t("avatar.portraitEdit")))), (0, react.createElement)("p", { style: {
1351
+ ...require_theme.dshSettingsHelpStyle,
1352
+ margin: 0,
1353
+ gridColumn: "1 / -1"
1354
+ } }, t(services.configurationDraft ? "avatar.portraitDraftHint" : "avatar.portraitLocalHint")), !open ? null : (0, react.createElement)("div", {
1355
+ "data-codingns-portrait-editor": true,
1356
+ "aria-busy": pending,
1357
+ style: {
1358
+ display: "grid",
1359
+ gap: 12,
1360
+ padding: 12,
1361
+ gridColumn: "1 / -1",
1362
+ border: `1px solid ${require_theme.dshThemeColor.border}`,
1363
+ borderRadius: 10,
1364
+ minWidth: 0
1365
+ }
1366
+ }, source === void 0 ? null : (0, react.createElement)("div", {
1367
+ "data-codingns-portrait-cropper": true,
1368
+ style: {
1369
+ position: "relative",
1370
+ height: 270,
1371
+ width: "100%",
1372
+ minWidth: 0,
1373
+ overflow: "hidden",
1374
+ borderRadius: 8,
1375
+ background: require_theme.dshThemeColor.surfaceSubtle,
1376
+ pointerEvents: locked ? "none" : "auto"
1377
+ }
1378
+ }, (0, react.createElement)(Cropper, {
1379
+ image: source.url,
1380
+ crop,
1381
+ zoom,
1382
+ aspect: 1,
1383
+ cropShape: "round",
1384
+ showGrid: false,
1385
+ minZoom: 1,
1386
+ maxZoom: 8,
1387
+ zoomWithScroll: false,
1388
+ onCropChange: (next) => {
1389
+ if (!locked) setCrop(next);
1390
+ },
1391
+ onZoomChange: (next) => {
1392
+ if (!locked) setZoom(next);
1393
+ },
1394
+ onCropAreaChange: changedArea,
1395
+ ...source.initial === void 0 ? {} : { initialCroppedAreaPercentages: source.initial },
1396
+ cropperProps: {
1397
+ "aria-label": t("avatar.portraitCropArea"),
1398
+ "aria-disabled": locked,
1399
+ tabIndex: locked ? -1 : 0
1400
+ }
1401
+ })), source === void 0 ? null : (0, react.createElement)("label", { style: {
1402
+ display: "grid",
1403
+ gap: 6
1404
+ } }, t("avatar.portraitZoom"), (0, react.createElement)("input", {
1405
+ type: "range",
1406
+ min: 1,
1407
+ max: 8,
1408
+ step: .01,
1409
+ value: zoom,
1410
+ disabled: locked,
1411
+ "data-codingns-portrait-zoom": true,
1412
+ onChange: (event) => setZoom(Number(event.currentTarget.value)),
1413
+ style: {
1414
+ width: "100%",
1415
+ margin: 0
1416
+ }
1417
+ })), source === void 0 || area === void 0 ? null : (0, react.createElement)("div", { style: {
1418
+ display: "flex",
1419
+ alignItems: "center",
1420
+ gap: 10
1421
+ } }, (0, react.createElement)("span", {
1422
+ "data-codingns-portrait-crop-preview": true,
1423
+ "aria-hidden": true,
1424
+ style: {
1425
+ position: "relative",
1426
+ overflow: "hidden",
1427
+ borderRadius: "50%",
1428
+ width: 56,
1429
+ height: 56,
1430
+ flex: "0 0 56px"
1431
+ }
1432
+ }, (0, react.createElement)("img", {
1433
+ src: source.url,
1434
+ alt: "",
1435
+ draggable: false,
1436
+ style: {
1437
+ position: "absolute",
1438
+ maxWidth: "none",
1439
+ width: source.preview.width / area.width * 56,
1440
+ height: source.preview.height / area.height * 56,
1441
+ left: -area.x / area.width * 56,
1442
+ top: -area.y / area.height * 56
1443
+ }
1444
+ })), (0, react.createElement)("span", { style: require_theme.dshSettingsHelpStyle }, t("avatar.portraitDragHint"))), !pending ? null : (0, react.createElement)("span", {
1445
+ role: "status",
1446
+ style: require_theme.dshSettingsHelpStyle
1447
+ }, t(source === void 0 ? "avatar.portraitLoading" : "avatar.portraitSaving")), error === "" ? null : (0, react.createElement)("span", {
1448
+ role: "alert",
1449
+ style: {
1450
+ ...require_theme.dshSettingsHelpStyle,
1451
+ color: require_theme.dshThemeColor.error
1452
+ }
1453
+ }, error), (0, react.createElement)("div", { style: {
1454
+ display: "flex",
1455
+ flexWrap: "wrap",
1456
+ justifyContent: "flex-end",
1457
+ gap: 8
1458
+ } }, (0, react.createElement)("button", {
1459
+ type: "button",
1460
+ disabled: locked,
1461
+ style: require_theme.dshSettingsButtonStyle,
1462
+ onClick: close
1463
+ }, t("avatar.portraitCancel")), (0, react.createElement)("button", {
1464
+ type: "button",
1465
+ disabled: disabled || pending || area === void 0,
1466
+ style: require_theme.dshSettingsPrimaryButtonStyle,
1467
+ "data-codingns-portrait-save": true,
1468
+ onClick: () => {
1469
+ save();
1470
+ }
1471
+ }, t(services.configurationDraft ? "avatar.portraitConfirm" : "avatar.portraitSave")))));
1472
+ }
1473
+ //#endregion
1474
+ //#region src/client/avatar/catalog-picker.ts
1475
+ var import_react_dom = require_react_dom$1.require_react_dom();
1476
+ /** 合并展示选项,沿用安装后的模型 ID 去重,不把目录元数据写进设置清单。 */
1477
+ function assistantAvatarChoices(appearance, entries, t, includeLegacy = true) {
1478
+ const choices = /* @__PURE__ */ new Map();
1479
+ for (const model of require_manager.listAssistantAvatars(appearance, includeLegacy)) {
1480
+ const builtinLabel = require_manager.builtinAssistantAvatarLabelKey(model.id);
1481
+ const labelKey = builtinLabel ?? (appearance.models.some((registered) => registered.id === model.id) ? void 0 : require_manager.getAssistantAvatarPreset(model.id)?.labelKey);
1482
+ const name = labelKey === void 0 ? model.name : t(labelKey);
1483
+ choices.set(model.id, {
1484
+ id: model.id,
1485
+ label: name,
1486
+ thirdParty: builtinLabel === void 0
1487
+ });
1488
+ }
1489
+ for (const entry of entries) {
1490
+ const id = `catalog-${entry.id}`;
1491
+ if (!choices.has(id)) choices.set(id, {
1492
+ id,
1493
+ label: entry.name,
1494
+ thirdParty: true,
1495
+ catalog: entry
1496
+ });
1497
+ }
1498
+ return [...choices.values()];
1499
+ }
1500
+ /** 内置形象只有名称,第三方形象在名称旁显示独立彩色标签。 */
1501
+ function AssistantAvatarChoiceLabel({ choice, t }) {
1502
+ return (0, react.createElement)("span", { style: {
1503
+ display: "flex",
1504
+ alignItems: "center",
1505
+ gap: 8,
1506
+ minWidth: 0
1507
+ } }, (0, react.createElement)("span", { style: {
1508
+ overflow: "hidden",
1509
+ textOverflow: "ellipsis",
1510
+ whiteSpace: "nowrap"
1511
+ } }, choice.label), !choice.thirdParty ? null : (0, react.createElement)("span", {
1512
+ "data-codingns-avatar-third-party-badge": true,
1513
+ style: {
1514
+ flexShrink: 0,
1515
+ padding: "1px 6px",
1516
+ borderRadius: 4,
1517
+ background: "#2563eb",
1518
+ color: "#fff",
1519
+ fontSize: 11,
1520
+ fontWeight: 500,
1521
+ lineHeight: "18px",
1522
+ whiteSpace: "nowrap"
1523
+ }
1524
+ }, t("avatar.thirdPartyBadge")));
1525
+ }
1526
+ /** 一个选择入口同时服务内置、已安装和待安装形象。 */
1527
+ function AssistantAvatarPicker(props) {
1528
+ const [open, setOpen] = (0, react.useState)(false);
1529
+ const id = (0, react.useId)();
1530
+ (0, react.useEffect)(() => {
1531
+ if (props.disabled) setOpen(false);
1532
+ }, [props.disabled]);
1533
+ return (0, react.createElement)(AssistantAvatarPickerView, {
1534
+ ...props,
1535
+ id,
1536
+ open,
1537
+ onOpenChange: setOpen
1538
+ });
1539
+ }
1540
+ /** 展开状态与展示分离;菜单行为由 DSH 原语负责,业务只处理有效选择。 */
1541
+ function AssistantAvatarPickerView({ choices, value, disabled, t, onChoose, label = t("avatar.selected"), id, open, onOpenChange }) {
1542
+ const selected = choices.find((choice) => choice.id === value);
1543
+ const expanded = open && !disabled;
1544
+ const name = selected === void 0 ? value : selected.thirdParty ? t("avatar.externalName", { name: selected.label }) : selected.label;
1545
+ const anchor = (0, react.createElement)("button", {
1546
+ id,
1547
+ type: "button",
1548
+ disabled,
1549
+ "aria-label": `${label}: ${name}`,
1550
+ "aria-haspopup": "menu",
1551
+ "aria-expanded": expanded,
1552
+ "data-codingns-avatar-list": true,
1553
+ "data-codingns-avatar-selected": value,
1554
+ style: {
1555
+ ...require_theme.dshSettingsFieldStyle,
1556
+ display: "flex",
1557
+ alignItems: "center",
1558
+ justifyContent: "space-between",
1559
+ gap: 8,
1560
+ textAlign: "left",
1561
+ cursor: disabled ? "default" : "pointer"
1562
+ },
1563
+ onClick: () => {
1564
+ if (!disabled) onOpenChange(!expanded);
1565
+ },
1566
+ onKeyDown: (event) => {
1567
+ if (disabled || expanded || event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
1568
+ event.preventDefault();
1569
+ onOpenChange(true);
1570
+ }
1571
+ }, selected === void 0 ? name : (0, react.createElement)(AssistantAvatarChoiceLabel, {
1572
+ choice: selected,
1573
+ t
1574
+ }), (0, react.createElement)("span", {
1575
+ "aria-hidden": true,
1576
+ style: {
1577
+ display: "flex",
1578
+ flexShrink: 0
1579
+ }
1580
+ }, (0, react.createElement)(require_primitives_adapter.resolveChevronDownIcon(), { size: 14 })));
1581
+ return (0, react.createElement)("label", {
1582
+ htmlFor: id,
1583
+ style: require_assistant_workbench.assistantSettingFieldStyle
1584
+ }, (0, react.createElement)("span", null, label), (0, react.createElement)("style", null, ".codingns4dsh-avatar-picker{width:100%;min-width:0}.codingns4dsh-avatar-menu{z-index:10002;width:min(360px,calc(100vw - 24px));max-width:calc(100vw - 24px)}"), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1585
+ open: expanded,
1586
+ anchor,
1587
+ portal: true,
1588
+ autoFocus: true,
1589
+ className: "codingns4dsh-avatar-picker",
1590
+ listClassName: "codingns4dsh-avatar-menu",
1591
+ selectedId: value,
1592
+ items: choices.map((choice) => ({
1593
+ id: choice.id,
1594
+ label: (0, react.createElement)(AssistantAvatarChoiceLabel, {
1595
+ choice,
1596
+ t
1597
+ }),
1598
+ disabled: disabled || choice.disabled === true
1599
+ })),
1600
+ onClose: () => onOpenChange(false),
1601
+ onSelect: (choiceId) => {
1602
+ const choice = choices.find((item) => item.id === choiceId);
1603
+ if (disabled || choice === void 0 || choice.disabled) return;
1604
+ onOpenChange(false);
1605
+ onChoose(choice);
1606
+ }
1607
+ }));
1608
+ }
1609
+ //#endregion
1610
+ //#region src/client/avatar/engine.ts
1611
+ /**
1612
+ * 引擎只在用户选择 Live2D 形象时准备:先探测 Host 已就绪的引擎,
1613
+ * 未安装则确认许可后自动下载安装;安装失败不改变当前形象。
1614
+ */
1615
+ function useAssistantAvatarEngine(options) {
1616
+ const { services, manager, appearance, disabled, notify } = options;
1617
+ const t = require_transport.useCodingNsTranslator(services.locale);
1618
+ const [status, setStatus] = (0, react.useState)();
1619
+ const [installing, setInstalling] = (0, react.useState)(false);
1620
+ const [cancelled, setCancelled] = (0, react.useState)(false);
1621
+ const [error, setError] = (0, react.useState)("");
1622
+ const [consentOpen, setConsentOpen] = (0, react.useState)(false);
1623
+ const [agreed, setAgreed] = (0, react.useState)(false);
1624
+ const installer = (0, react.useRef)();
1625
+ const probe = (0, react.useRef)();
1626
+ const pending = (0, react.useRef)();
1627
+ const mounted = (0, react.useRef)(true);
1628
+ (0, react.useEffect)(() => {
1629
+ mounted.current = true;
1630
+ return () => {
1631
+ mounted.current = false;
1632
+ probe.current?.abort();
1633
+ installer.current?.abort();
1634
+ };
1635
+ }, []);
1636
+ (0, react.useEffect)(() => {
1637
+ const controller = new AbortController();
1638
+ manager.engineStatus(controller.signal).then((value) => {
1639
+ if (!controller.signal.aborted && mounted.current) setStatus(value);
1640
+ }).catch(() => void 0);
1641
+ return () => controller.abort();
1642
+ }, [manager]);
1643
+ const runInstall = async (onReady) => {
1644
+ if (disabled || !mounted.current || installer.current !== void 0) return false;
1645
+ const controller = new AbortController();
1646
+ installer.current = controller;
1647
+ pending.current = onReady;
1648
+ setInstalling(true);
1649
+ setError("");
1650
+ setCancelled(false);
1651
+ try {
1652
+ const next = await manager.installEngine(controller.signal);
1653
+ if (controller.signal.aborted) return false;
1654
+ setStatus(next);
1655
+ await onReady();
1656
+ pending.current = void 0;
1657
+ notify?.({
1658
+ kind: "success",
1659
+ message: t("avatar.engineInstalled")
1660
+ });
1661
+ return true;
1662
+ } catch (failure) {
1663
+ if (!controller.signal.aborted && mounted.current) setError(errorMessage$2(failure));
1664
+ return false;
1665
+ } finally {
1666
+ if (installer.current === controller) installer.current = void 0;
1667
+ if (mounted.current) setInstalling(false);
1668
+ }
1669
+ };
1670
+ const ensure = async (onReady) => {
1671
+ if (disabled || installing || !mounted.current || installer.current !== void 0) return false;
1672
+ probe.current?.abort();
1673
+ const controller = new AbortController();
1674
+ probe.current = controller;
1675
+ setError("");
1676
+ let current = status;
1677
+ try {
1678
+ current = await manager.engineStatus(controller.signal);
1679
+ } catch {} finally {
1680
+ if (probe.current === controller) probe.current = void 0;
1681
+ }
1682
+ if (controller.signal.aborted || !mounted.current) return false;
1683
+ if (current !== void 0) setStatus(current);
1684
+ if (current?.installed === true) {
1685
+ await onReady();
1686
+ return true;
1687
+ }
1688
+ if (!require_manager.hasAssistantAvatarEngineConsent(appearance.engineConsent)) {
1689
+ pending.current = onReady;
1690
+ setAgreed(false);
1691
+ setConsentOpen(true);
1692
+ return false;
1693
+ }
1694
+ return await runInstall(onReady);
1695
+ };
1696
+ return {
1697
+ status,
1698
+ installing,
1699
+ cancelled,
1700
+ error,
1701
+ consentOpen,
1702
+ agreed,
1703
+ ensure,
1704
+ setAgreed: (value) => {
1705
+ setAgreed(value);
1706
+ },
1707
+ acceptConsent: async () => {
1708
+ try {
1709
+ await manager.setEngineEnabled(true);
1710
+ if (!mounted.current) return;
1711
+ setConsentOpen(false);
1712
+ setAgreed(false);
1713
+ const next = pending.current;
1714
+ if (next !== void 0) await runInstall(next);
1715
+ } catch (failure) {
1716
+ if (mounted.current) setError(errorMessage$2(failure));
1717
+ }
1718
+ },
1719
+ cancelConsent: () => {
1720
+ pending.current = void 0;
1721
+ setAgreed(false);
1722
+ setConsentOpen(false);
1723
+ },
1724
+ cancelInstall: () => {
1725
+ if (installer.current !== void 0) {
1726
+ setCancelled(true);
1727
+ installer.current.abort();
1728
+ }
1729
+ },
1730
+ retry: () => {
1731
+ const next = pending.current;
1732
+ if (next !== void 0) runInstall(next);
1733
+ }
1734
+ };
1735
+ }
1736
+ /** 许可确认使用原生模态;未同意时不产生任何下载或设置写入。 */
1737
+ function AssistantAvatarEngineDialog({ controller, t }) {
1738
+ const ref = (0, react.useRef)(null);
1739
+ const id = (0, react.useId)();
1740
+ const open = controller.consentOpen;
1741
+ (0, react.useEffect)(() => {
1742
+ if (!open) return;
1743
+ const opener = document.activeElement;
1744
+ const dialog = ref.current;
1745
+ dialog?.showModal();
1746
+ return () => {
1747
+ if (dialog?.open) dialog.close();
1748
+ if (opener?.isConnected) opener.focus();
1749
+ };
1750
+ }, [open]);
1751
+ if (!open) return null;
1752
+ return (0, react.createElement)("dialog", {
1753
+ ref,
1754
+ "aria-labelledby": id,
1755
+ "data-codingns-avatar-engine-dialog": true,
1756
+ onCancel: (event) => {
1757
+ event.preventDefault();
1758
+ controller.cancelConsent();
1759
+ },
1760
+ onKeyDown: (event) => {
1761
+ if (event.key === "Escape") {
1762
+ event.preventDefault();
1763
+ event.stopPropagation();
1764
+ controller.cancelConsent();
1765
+ }
1766
+ },
1767
+ style: {
1768
+ maxWidth: "min(480px, calc(100vw - 32px))",
1769
+ maxHeight: "calc(100dvh - 32px)",
1770
+ overflowY: "auto",
1771
+ boxSizing: "border-box",
1772
+ padding: 24,
1773
+ borderRadius: 16,
1774
+ margin: "auto",
1775
+ border: `1px solid ${require_theme.dshThemeColor.border}`,
1776
+ background: require_theme.dshThemeColor.pageBackground,
1777
+ color: require_theme.dshThemeColor.labelPrimary,
1778
+ boxShadow: require_theme.dshThemeColor.subtleShadow
1779
+ }
1780
+ }, (0, react.createElement)("strong", {
1781
+ id,
1782
+ style: { fontSize: 16 }
1783
+ }, t("avatar.engineTerms", { version: require_manager.ASSISTANT_AVATAR_ENGINE_CONSENT_VERSION })), (0, react.createElement)("div", { style: {
1784
+ display: "grid",
1785
+ gap: 16,
1786
+ marginTop: 14
1787
+ } }, (0, react.createElement)("p", { style: {
1788
+ ...require_theme.dshSettingsHelpStyle,
1789
+ margin: 0,
1790
+ lineHeight: 1.7
1791
+ } }, t("avatar.engineTermsContent")), (0, react.createElement)("label", { style: require_assistant_workbench.assistantSettingCheckboxStyle }, (0, react.createElement)("input", {
1792
+ type: "checkbox",
1793
+ checked: controller.agreed,
1794
+ onChange: (event) => controller.setAgreed(event.currentTarget.checked)
1795
+ }), t("avatar.engineAgree")), (0, react.createElement)("div", { style: {
1796
+ display: "flex",
1797
+ justifyContent: "flex-end",
1798
+ gap: 8
1799
+ } }, (0, react.createElement)("button", {
1800
+ type: "button",
1801
+ autoFocus: true,
1802
+ style: require_theme.dshSettingsButtonStyle,
1803
+ onClick: controller.cancelConsent
1804
+ }, t("avatar.cancel")), (0, react.createElement)("button", {
1805
+ type: "button",
1806
+ disabled: !controller.agreed,
1807
+ style: require_theme.dshSettingsPrimaryButtonStyle,
1808
+ onClick: () => {
1809
+ if (controller.agreed) controller.acceptConsent();
1810
+ }
1811
+ }, t("avatar.engineConfirm")))));
1812
+ }
1813
+ /** 安装中显示不确定进度与取消;失败显示原因和重试,不改变当前形象。 */
1814
+ function AssistantAvatarEngineProgress({ controller, t }) {
1815
+ if (controller.installing) return (0, react.createElement)("div", {
1816
+ role: "status",
1817
+ "data-codingns-avatar-engine-progress": true,
1818
+ style: {
1819
+ display: "grid",
1820
+ gap: 8
1821
+ }
1822
+ }, (0, react.createElement)("span", null, t("avatar.engineInstalling")), (0, react.createElement)("progress", {
1823
+ "aria-label": t("avatar.engineInstalling"),
1824
+ style: { width: "100%" }
1825
+ }), (0, react.createElement)("button", {
1826
+ type: "button",
1827
+ style: {
1828
+ ...require_theme.dshSettingsButtonStyle,
1829
+ justifySelf: "start"
1830
+ },
1831
+ onClick: controller.cancelInstall
1832
+ }, t("avatar.engineCancel")));
1833
+ if (controller.error === "") {
1834
+ if (!controller.cancelled) return null;
1835
+ return (0, react.createElement)("div", {
1836
+ role: "status",
1837
+ "data-codingns-avatar-engine-cancelled": true,
1838
+ style: {
1839
+ display: "grid",
1840
+ gap: 8
1841
+ }
1842
+ }, (0, react.createElement)("span", null, t("avatar.engineCancelled")), (0, react.createElement)("button", {
1843
+ type: "button",
1844
+ style: {
1845
+ ...require_theme.dshSettingsButtonStyle,
1846
+ justifySelf: "start"
1847
+ },
1848
+ onClick: controller.retry
1849
+ }, t("avatar.engineRetry")));
1850
+ }
1851
+ return (0, react.createElement)("div", {
1852
+ role: "alert",
1853
+ "data-codingns-avatar-engine-error": true,
1854
+ style: {
1855
+ display: "grid",
1856
+ gap: 8,
1857
+ color: require_theme.dshThemeColor.error,
1858
+ fontSize: 13
1859
+ }
1860
+ }, (0, react.createElement)("span", { style: { overflowWrap: "anywhere" } }, t("avatar.engineInstallFailed", { reason: controller.error })), (0, react.createElement)("button", {
1861
+ type: "button",
1862
+ style: {
1863
+ ...require_theme.dshSettingsButtonStyle,
1864
+ justifySelf: "start",
1865
+ color: require_theme.dshThemeColor.error
1866
+ },
1867
+ onClick: controller.retry
1868
+ }, t("avatar.engineRetry")));
1869
+ }
1870
+ function errorMessage$2(error) {
1871
+ return error instanceof Error ? error.message : String(error);
1872
+ }
1873
+ //#endregion
1874
+ //#region src/client/avatar/temporary-preview.ts
1875
+ /** 挂载即启动预览;离开后隔离所有回调,避免旧下载覆盖新形象或关闭后的界面。 */
1876
+ function startAssistantAvatarTemporaryPreview(manager, entry, observer) {
1877
+ let active = true;
1878
+ const session = new AssistantAvatarTemporaryPreviewSession(manager, (error) => {
1879
+ if (active) observer.onError(error);
1880
+ });
1881
+ session.load(entry).then((preview) => {
1882
+ if (active) observer.onLoaded(preview);
1883
+ }).catch((error) => {
1884
+ if (active) observer.onError(error);
1885
+ }).finally(() => {
1886
+ if (active) observer.onSettled();
1887
+ });
1888
+ return { dispose: async () => {
1889
+ active = false;
1890
+ await session.dispose();
1891
+ } };
1892
+ }
1893
+ /** 客户端拥有一个临时预览租约,取消先释放,迟到答复再次释放,绝不登记到设置。 */
1894
+ var AssistantAvatarTemporaryPreviewSession = class {
1895
+ manager;
1896
+ onExpired;
1897
+ lease;
1898
+ controller = new AbortController();
1899
+ disposed = false;
1900
+ timer;
1901
+ constructor(manager, onExpired = () => {}, lease = crypto.randomUUID()) {
1902
+ this.manager = manager;
1903
+ this.onExpired = onExpired;
1904
+ this.lease = lease;
1905
+ }
1906
+ async load(entry) {
1907
+ this.controller.signal.throwIfAborted();
1908
+ try {
1909
+ this.timer = setInterval(() => {
1910
+ this.manager.keepPreview(this.lease).catch((error) => {
1911
+ if (this.disposed) return;
1912
+ this.onExpired(error);
1913
+ this.dispose().catch(() => void 0);
1914
+ });
1915
+ }, 6e4);
1916
+ const preview = await this.manager.previewCatalog(entry.id, entry.revision, this.lease, this.controller.signal);
1917
+ if (this.disposed) {
1918
+ await this.manager.releasePreview(this.lease);
1919
+ this.controller.signal.throwIfAborted();
1920
+ }
1921
+ return preview;
1922
+ } catch (error) {
1923
+ await this.dispose().catch(() => void 0);
1924
+ throw error;
1925
+ }
1926
+ }
1927
+ async dispose() {
1928
+ if (this.disposed) return;
1929
+ this.disposed = true;
1930
+ this.controller.abort();
1931
+ if (this.timer !== void 0) clearInterval(this.timer);
1932
+ await this.manager.releasePreview(this.lease);
1933
+ }
1934
+ };
1935
+ //#endregion
1936
+ //#region src/client/avatar/catalog-panel.ts
1937
+ const buttonStyle = {
1938
+ ...require_theme.dshSettingsButtonStyle,
1939
+ justifySelf: "start"
1940
+ };
1941
+ const helpStyle$1 = {
1942
+ ...require_theme.dshSettingsHelpStyle,
1943
+ margin: 0
1944
+ };
1945
+ const checkStyle = require_assistant_workbench.assistantSettingCheckboxStyle;
1946
+ /** 目录与已登记角色共用选择入口,协议和素材安装仍分别确认。 */
1947
+ function AssistantAvatarCatalogPanel(props) {
1948
+ const { services, manager, appearance, disabled, notify, active = true, previewTarget, onPreviewChange } = props;
1949
+ const t = require_transport.useCodingNsTranslator(services.locale);
1950
+ const [agreed, setAgreed] = (0, react.useState)(false);
1951
+ const [agreementOpen, setAgreementOpen] = (0, react.useState)(false);
1952
+ const [pending, setPending] = (0, react.useState)(false);
1953
+ const [installing, setInstalling] = (0, react.useState)(false);
1954
+ const [preview, setPreview] = (0, react.useState)();
1955
+ const [error, setError] = (0, react.useState)("");
1956
+ const accepted = require_manager.hasAssistantAvatarConsent(appearance.thirdPartyConsent);
1957
+ const consent = accepted ? `${appearance.thirdPartyConsent.version}:${appearance.thirdPartyConsent.acceptedAt}` : "";
1958
+ const catalog = useAssistantAvatarCatalog(manager, consent);
1959
+ const engine = useAssistantAvatarEngine({
1960
+ services,
1961
+ manager,
1962
+ appearance,
1963
+ disabled,
1964
+ notify
1965
+ });
1966
+ const current = require_manager.selectedAssistantAvatar(appearance);
1967
+ const choices = assistantAvatarChoices(appearance, catalog.entries, t);
1968
+ const selected = preview?.consent === consent ? catalog.entries.find((entry) => entry.id === preview.id) : void 0;
1969
+ (0, react.useEffect)(() => {
1970
+ onPreviewChange?.(active ? selected : void 0);
1971
+ return () => onPreviewChange?.(void 0);
1972
+ }, [
1973
+ active,
1974
+ selected,
1975
+ onPreviewChange
1976
+ ]);
1977
+ const busy = disabled || pending || installing || engine.installing;
1978
+ const choose = async (choice) => {
1979
+ if (busy) return;
1980
+ setError("");
1981
+ setPending(true);
1982
+ try {
1983
+ const select = async () => {
1984
+ if (choice.catalog !== void 0) {
1985
+ const id = choice.catalog.id;
1986
+ setPreview((current) => ({
1987
+ id,
1988
+ consent,
1989
+ selection: (current?.selection ?? 0) + 1
1990
+ }));
1991
+ return;
1992
+ }
1993
+ setPreview(void 0);
1994
+ await manager.select(choice.id);
1995
+ if (!services.configurationDraft) notify({
1996
+ kind: "success",
1997
+ message: t("avatar.saved")
1998
+ });
1999
+ };
2000
+ if (choice.catalog === void 0 ? manager.list().find((model) => model.id === choice.id)?.renderer === "live2d" : ["dsh-live2d-pet", "cubism-model"].includes(choice.catalog.format)) await engine.ensure(select);
2001
+ else await select();
2002
+ } catch (failure) {
2003
+ setError(errorMessage$1(failure));
2004
+ notify({
2005
+ kind: "error",
2006
+ message: errorMessage$1(failure)
2007
+ });
2008
+ } finally {
2009
+ setPending(false);
2010
+ }
2011
+ };
2012
+ const changeConsent = async (enabled) => {
2013
+ if (busy || enabled && !agreed) return;
2014
+ setPending(true);
2015
+ setError("");
2016
+ try {
2017
+ await manager.setThirdPartyEnabled(enabled);
2018
+ setAgreed(false);
2019
+ setAgreementOpen(false);
2020
+ } catch (failure) {
2021
+ setError(errorMessage$1(failure));
2022
+ notify({
2023
+ kind: "error",
2024
+ message: errorMessage$1(failure)
2025
+ });
2026
+ } finally {
2027
+ setPending(false);
2028
+ }
2029
+ };
2030
+ const previewNode = selected === void 0 || !active ? null : (0, react.createElement)(AssistantAvatarCatalogPreview, {
2031
+ ...props,
2032
+ selected,
2033
+ disabled: disabled || pending,
2034
+ key: `${consent}:${selected.id}:${selected.revision}:${preview?.selection}`,
2035
+ onPending: setInstalling,
2036
+ onInstalled: () => setPreview(void 0)
2037
+ });
2038
+ return (0, react.createElement)("section", {
2039
+ "data-codingns-third-party-avatars": true,
2040
+ "aria-label": t("avatar.thirdPartyTitle"),
2041
+ style: {
2042
+ ...require_assistant_workbench.assistantSettingTextStyle,
2043
+ display: "grid",
2044
+ gap: 20,
2045
+ ...previewNode !== null && !previewTarget ? { gridTemplateColumns: "repeat(auto-fit,minmax(min(100%,260px),1fr))" } : {}
2046
+ }
2047
+ }, (0, react.createElement)("div", { style: {
2048
+ display: "grid",
2049
+ gap: 12,
2050
+ alignContent: "start"
2051
+ } }, (0, react.createElement)(AssistantAvatarPicker, {
2052
+ choices,
2053
+ value: selected === void 0 ? appearance.selectedId : `catalog-${selected.id}`,
2054
+ disabled: busy,
2055
+ t,
2056
+ onChoose: (choice) => {
2057
+ choose(choice);
2058
+ }
2059
+ }), current.renderer !== "live2d" || engine.status?.installed !== false ? null : (0, react.createElement)("div", {
2060
+ "data-codingns-avatar-engine-missing": true,
2061
+ style: {
2062
+ display: "grid",
2063
+ gap: 8
2064
+ }
2065
+ }, (0, react.createElement)("p", { style: helpStyle$1 }, t("avatar.engineMissing")), (0, react.createElement)("button", {
2066
+ type: "button",
2067
+ disabled: busy,
2068
+ style: buttonStyle,
2069
+ onClick: () => {
2070
+ engine.ensure(() => void 0);
2071
+ }
2072
+ }, t("avatar.engineInstall"))), (0, react.createElement)(AssistantAvatarEngineProgress, {
2073
+ controller: engine,
2074
+ t
2075
+ }), (0, react.createElement)(AssistantAvatarThirdPartyToggle, {
2076
+ accepted,
2077
+ disabled: busy,
2078
+ t,
2079
+ onRequest: () => {
2080
+ setAgreed(false);
2081
+ setAgreementOpen(true);
2082
+ },
2083
+ onDisable: () => {
2084
+ changeConsent(false);
2085
+ }
2086
+ }), !agreementOpen ? null : (0, react.createElement)(AssistantAvatarConsentDialog, {
2087
+ agreed,
2088
+ disabled: busy,
2089
+ t,
2090
+ onChange: setAgreed,
2091
+ onAccept: () => {
2092
+ changeConsent(true);
2093
+ },
2094
+ onCancel: () => {
2095
+ if (!pending) {
2096
+ setAgreementOpen(false);
2097
+ setAgreed(false);
2098
+ }
2099
+ }
2100
+ }), !catalog.loading ? null : (0, react.createElement)("div", {
2101
+ role: "status",
2102
+ style: helpStyle$1
2103
+ }, t("avatar.catalogLoading")), !accepted || catalog.loading || catalog.entries.length > 0 || catalog.error ? null : (0, react.createElement)("p", { style: helpStyle$1 }, t("avatar.catalogEmpty")), !catalog.error ? null : (0, react.createElement)("div", {
2104
+ role: "alert",
2105
+ style: {
2106
+ display: "grid",
2107
+ gap: 8,
2108
+ color: require_theme.dshThemeColor.error,
2109
+ fontSize: 13
2110
+ }
2111
+ }, catalog.error, (0, react.createElement)("button", {
2112
+ type: "button",
2113
+ disabled: busy || catalog.loading,
2114
+ style: buttonStyle,
2115
+ onClick: catalog.reload
2116
+ }, t("avatar.catalogRetry"))), !error ? null : (0, react.createElement)("div", {
2117
+ role: "alert",
2118
+ style: {
2119
+ color: require_theme.dshThemeColor.error,
2120
+ fontSize: 13
2121
+ }
2122
+ }, error), (0, react.createElement)(AssistantAvatarEngineDialog, {
2123
+ controller: engine,
2124
+ t
2125
+ })), previewNode === null ? null : (0, react.createElement)(AssistantAvatarCatalogPreviewRegion, {
2126
+ target: previewTarget,
2127
+ children: previewNode
2128
+ }));
2129
+ }
2130
+ /** 预览 DOM 归右侧目标所有,独立设置入口则提供同级侧栏。 */
2131
+ function AssistantAvatarCatalogPreviewRegion({ target, children }) {
2132
+ return target ? (0, import_react_dom.createPortal)(children, target) : (0, react.createElement)("aside", { "data-codingns-avatar-preview-region": true }, children);
2133
+ }
2134
+ /** 仅有效协议允许读取元数据;协议变化和卸载会取消旧请求并隔离迟到答复。 */
2135
+ function useAssistantAvatarCatalog(manager, consent) {
2136
+ const [retry, setRetry] = (0, react.useState)(0);
2137
+ const [catalog, setCatalog] = (0, react.useState)({
2138
+ consent: "",
2139
+ entries: [],
2140
+ loading: false,
2141
+ error: ""
2142
+ });
2143
+ (0, react.useEffect)(() => {
2144
+ if (!consent) return;
2145
+ const controller = new AbortController();
2146
+ setCatalog({
2147
+ consent,
2148
+ entries: [],
2149
+ loading: true,
2150
+ error: ""
2151
+ });
2152
+ manager.getCatalog(controller.signal).then((entries) => {
2153
+ if (!controller.signal.aborted) setCatalog({
2154
+ consent,
2155
+ entries,
2156
+ loading: false,
2157
+ error: ""
2158
+ });
2159
+ }).catch((failure) => {
2160
+ if (!controller.signal.aborted) setCatalog({
2161
+ consent,
2162
+ entries: [],
2163
+ loading: false,
2164
+ error: errorMessage$1(failure)
2165
+ });
2166
+ });
2167
+ return () => controller.abort();
2168
+ }, [
2169
+ manager,
2170
+ consent,
2171
+ retry
2172
+ ]);
2173
+ const current = consent !== "" && catalog.consent === consent;
2174
+ return {
2175
+ entries: current ? catalog.entries : [],
2176
+ loading: consent !== "" && (!current || catalog.loading),
2177
+ error: current ? catalog.error : "",
2178
+ reload: () => setRetry((value) => value + 1)
2179
+ };
2180
+ }
2181
+ /** 勾选只请求展示协议,明确同意后立即加载目录;取消不会产生任何设置写入。 */
2182
+ function AssistantAvatarThirdPartyToggle({ accepted, disabled, t, onRequest, onDisable }) {
2183
+ return (0, react.createElement)("label", { style: require_assistant_workbench.assistantSettingSwitchStyle }, (0, react.createElement)("span", null, t("avatar.thirdPartyEnable")), (0, react.createElement)("input", {
2184
+ type: "checkbox",
2185
+ role: "switch",
2186
+ "aria-label": t("avatar.thirdPartyEnable"),
2187
+ checked: accepted,
2188
+ disabled,
2189
+ "data-codingns-third-party-enabled": true,
2190
+ onChange: (event) => {
2191
+ if (!disabled) {
2192
+ if (event.currentTarget.checked) onRequest();
2193
+ else onDisable();
2194
+ }
2195
+ }
2196
+ }));
2197
+ }
2198
+ /** 原生模态对话框限制背景焦点,关闭后恢复到触发协议的复选框。 */
2199
+ function AssistantAvatarConsentDialog(props) {
2200
+ const ref = (0, react.useRef)(null);
2201
+ const id = (0, react.useId)();
2202
+ (0, react.useEffect)(() => {
2203
+ const opener = document.activeElement;
2204
+ const dialog = ref.current;
2205
+ dialog?.showModal();
2206
+ return () => {
2207
+ if (dialog?.open) dialog.close();
2208
+ if (opener?.isConnected) opener.focus();
2209
+ };
2210
+ }, []);
2211
+ return (0, react.createElement)("dialog", {
2212
+ ref,
2213
+ "aria-labelledby": id,
2214
+ "data-codingns-avatar-consent-dialog": true,
2215
+ onCancel: (event) => {
2216
+ event.preventDefault();
2217
+ props.onCancel();
2218
+ },
2219
+ onKeyDown: (event) => {
2220
+ if (event.key === "Escape") {
2221
+ event.preventDefault();
2222
+ event.stopPropagation();
2223
+ props.onCancel();
2224
+ }
2225
+ },
2226
+ style: {
2227
+ maxWidth: "min(480px, calc(100vw - 32px))",
2228
+ maxHeight: "calc(100dvh - 32px)",
2229
+ overflowY: "auto",
2230
+ boxSizing: "border-box",
2231
+ padding: 24,
2232
+ borderRadius: 16,
2233
+ margin: "auto",
2234
+ border: `1px solid ${require_theme.dshThemeColor.border}`,
2235
+ background: require_theme.dshThemeColor.pageBackground,
2236
+ color: require_theme.dshThemeColor.labelPrimary,
2237
+ boxShadow: require_theme.dshThemeColor.subtleShadow
2238
+ }
2239
+ }, (0, react.createElement)("strong", {
2240
+ id,
2241
+ style: { fontSize: 16 }
2242
+ }, props.t("avatar.thirdPartyTerms", { version: require_manager.ASSISTANT_AVATAR_CONSENT_VERSION })), (0, react.createElement)(AssistantAvatarConsentPrompt, props));
2243
+ }
2244
+ function AssistantAvatarConsentPrompt({ agreed, disabled, t, onChange, onAccept, onCancel }) {
2245
+ return (0, react.createElement)("div", { style: {
2246
+ display: "grid",
2247
+ gap: 16,
2248
+ marginTop: 14
2249
+ } }, (0, react.createElement)("p", { style: {
2250
+ ...helpStyle$1,
2251
+ lineHeight: 1.7
2252
+ } }, t("avatar.thirdPartyTermsContent")), (0, react.createElement)("label", { style: checkStyle }, (0, react.createElement)("input", {
2253
+ type: "checkbox",
2254
+ checked: agreed,
2255
+ disabled,
2256
+ onChange: (event) => {
2257
+ if (!disabled) onChange(event.currentTarget.checked);
2258
+ }
2259
+ }), t("avatar.thirdPartyAgree")), (0, react.createElement)("div", { style: {
2260
+ display: "flex",
2261
+ justifyContent: "flex-end",
2262
+ gap: 8
2263
+ } }, (0, react.createElement)("button", {
2264
+ type: "button",
2265
+ autoFocus: true,
2266
+ disabled,
2267
+ style: require_theme.dshSettingsButtonStyle,
2268
+ onClick: onCancel
2269
+ }, t("avatar.cancel")), (0, react.createElement)("button", {
2270
+ type: "button",
2271
+ disabled: disabled || !agreed,
2272
+ style: require_theme.dshSettingsPrimaryButtonStyle,
2273
+ onClick: () => {
2274
+ if (!disabled && agreed) onAccept();
2275
+ }
2276
+ }, t("avatar.thirdPartyConfirm"))));
2277
+ }
2278
+ function AssistantAvatarCatalogPreview({ services, manager, appearance, disabled, notify, selected, onPending, onInstalled }) {
2279
+ const t = require_transport.useCodingNsTranslator(services.locale);
2280
+ const [licenseAccepted, setLicenseAccepted] = (0, react.useState)(false);
2281
+ const [temporary, setTemporary] = (0, react.useState)();
2282
+ const [previewLoading, setPreviewLoading] = (0, react.useState)(!disabled);
2283
+ const [pending, setPending] = (0, react.useState)(false);
2284
+ const [error, setError] = (0, react.useState)("");
2285
+ const installer = (0, react.useRef)();
2286
+ (0, react.useEffect)(() => () => {
2287
+ installer.current?.abort();
2288
+ onPending(false);
2289
+ }, [manager, onPending]);
2290
+ (0, react.useEffect)(() => {
2291
+ setTemporary(void 0);
2292
+ setPreviewLoading(!disabled);
2293
+ setError("");
2294
+ if (disabled) return;
2295
+ const preview = startAssistantAvatarTemporaryPreview(manager, {
2296
+ id: selected.id,
2297
+ revision: selected.revision
2298
+ }, {
2299
+ onLoaded: setTemporary,
2300
+ onError: (failure) => {
2301
+ setTemporary(void 0);
2302
+ setPreviewLoading(false);
2303
+ setError(errorMessage$1(failure));
2304
+ },
2305
+ onSettled: () => setPreviewLoading(false)
2306
+ });
2307
+ return () => {
2308
+ preview.dispose().catch(() => void 0);
2309
+ };
2310
+ }, [
2311
+ manager,
2312
+ disabled,
2313
+ selected.id,
2314
+ selected.revision
2315
+ ]);
2316
+ const installed = appearance.models.some((model) => model.id === `catalog-${selected.id}` && model.package?.installationId !== void 0);
2317
+ const full = appearance.models.length >= 21 && !installed;
2318
+ const install = async () => {
2319
+ if (disabled || pending || previewLoading || !licenseAccepted || full) return;
2320
+ const controller = new AbortController();
2321
+ installer.current = controller;
2322
+ setPending(true);
2323
+ onPending(true);
2324
+ setError("");
2325
+ try {
2326
+ await manager.installCatalog(selected.id, selected.revision, licenseAccepted, controller.signal, temporary?.lease);
2327
+ if (!controller.signal.aborted) {
2328
+ setLicenseAccepted(false);
2329
+ if (!services.configurationDraft) notify({
2330
+ kind: "success",
2331
+ message: t("avatar.saved")
2332
+ });
2333
+ onInstalled();
2334
+ }
2335
+ } catch (failure) {
2336
+ if (!controller.signal.aborted) {
2337
+ setError(errorMessage$1(failure));
2338
+ notify({
2339
+ kind: "error",
2340
+ message: errorMessage$1(failure)
2341
+ });
2342
+ }
2343
+ } finally {
2344
+ if (installer.current === controller) installer.current = void 0;
2345
+ if (!controller.signal.aborted) {
2346
+ setPending(false);
2347
+ onPending(false);
2348
+ }
2349
+ }
2350
+ };
2351
+ return (0, react.createElement)("div", {
2352
+ style: {
2353
+ display: "grid",
2354
+ gap: 12
2355
+ },
2356
+ "data-codingns-avatar-catalog-preview": true
2357
+ }, (0, react.createElement)("div", {
2358
+ style: {
2359
+ display: "grid",
2360
+ gap: 10
2361
+ },
2362
+ "data-codingns-avatar-catalog-selection": selected.id
2363
+ }, (0, react.createElement)("div", {
2364
+ "data-codingns-avatar-temporary-preview": true,
2365
+ "aria-busy": previewLoading || pending,
2366
+ style: {
2367
+ minHeight: 182,
2368
+ display: "grid",
2369
+ justifyItems: "center",
2370
+ alignItems: "center"
2371
+ }
2372
+ }, temporary === void 0 ? previewLoading || pending ? (0, react.createElement)("div", { style: {
2373
+ position: "relative",
2374
+ width: 168,
2375
+ height: 182,
2376
+ maxWidth: "100%"
2377
+ } }, (0, react.createElement)(require_manager.AssistantAvatarLoading, {
2378
+ progress: { phase: "resources" },
2379
+ size: 168,
2380
+ t,
2381
+ animationOnly: true
2382
+ })) : (0, react.createElement)("p", {
2383
+ role: "status",
2384
+ style: {
2385
+ ...helpStyle$1,
2386
+ textAlign: "center"
2387
+ }
2388
+ }, t(error ? "avatar.temporaryLoadFailed" : "avatar.temporaryUnavailable")) : (0, react.createElement)(require_manager.AssistantAvatarSlot, {
2389
+ services,
2390
+ model: temporary.model,
2391
+ state: "idle",
2392
+ surface: "dialog",
2393
+ size: 168,
2394
+ transient: true,
2395
+ showDiagnostics: false
2396
+ })), (0, react.createElement)("p", { style: helpStyle$1 }, t("avatar.catalogPreviewHint")), (0, react.createElement)("strong", null, selected.name), (0, react.createElement)("p", { style: helpStyle$1 }, selected.description), (0, react.createElement)("div", { style: helpStyle$1 }, t("avatar.packageAuthor", { author: selected.author })), (0, react.createElement)("div", { style: helpStyle$1 }, t("avatar.packageLicense", { license: selected.license })), (0, react.createElement)("p", { style: helpStyle$1 }, selected.remarks), (0, react.createElement)("div", { style: helpStyle$1 }, t("avatar.catalogSize", {
2397
+ size: `${(selected.bytes / 1024 / 1024).toFixed(2)} MB`,
2398
+ files: selected.files
2399
+ })), (0, react.createElement)("div", { style: {
2400
+ display: "flex",
2401
+ flexWrap: "wrap",
2402
+ gap: 14,
2403
+ fontSize: 13
2404
+ } }, (0, react.createElement)("a", {
2405
+ href: selected.repositoryUrl,
2406
+ target: "_blank",
2407
+ rel: "noopener noreferrer"
2408
+ }, t("avatar.packageSource")), (0, react.createElement)("a", {
2409
+ href: selected.licenseUrl,
2410
+ target: "_blank",
2411
+ rel: "noopener noreferrer"
2412
+ }, t("avatar.catalogLicenseLink")), (0, react.createElement)("a", {
2413
+ href: selected.homepage,
2414
+ target: "_blank",
2415
+ rel: "noopener noreferrer"
2416
+ }, t("avatar.packageHomepage"))), selected.format === "codex-pet" ? null : (0, react.createElement)("p", { style: helpStyle$1 }, t("avatar.live2dDependencyHint")), installed ? (0, react.createElement)("span", { style: {
2417
+ color: require_theme.dshThemeColor.success,
2418
+ fontSize: 13
2419
+ } }, t("avatar.catalogInstalled")) : null, (0, react.createElement)("label", { style: checkStyle }, (0, react.createElement)("input", {
2420
+ type: "checkbox",
2421
+ checked: licenseAccepted,
2422
+ disabled: disabled || pending,
2423
+ onChange: (event) => setLicenseAccepted(event.currentTarget.checked)
2424
+ }), t("avatar.catalogLicenseAgree")), (0, react.createElement)("button", {
2425
+ type: "button",
2426
+ disabled: disabled || pending || previewLoading || !licenseAccepted || full,
2427
+ style: buttonStyle,
2428
+ onClick: () => {
2429
+ install();
2430
+ }
2431
+ }, t("avatar.temporaryUse"))), !error ? null : (0, react.createElement)("div", {
2432
+ role: "alert",
2433
+ style: {
2434
+ color: require_theme.dshThemeColor.error,
2435
+ fontSize: 13
2436
+ }
2437
+ }, error));
2438
+ }
2439
+ function errorMessage$1(error) {
2440
+ return error instanceof Error ? error.message : String(error);
2441
+ }
2442
+ //#endregion
2443
+ //#region src/client/avatar/settings-panel.ts
2444
+ /** 对话内复用设置页表单;订阅同一存储并把保存反馈留在当前窗口。 */
2445
+ function AssistantAppearanceEditor({ services, enabled, ...previewProps }) {
2446
+ const store = services.settings;
2447
+ const subscribe = (0, react.useCallback)((listener) => store.subscribe(listener), [store]);
2448
+ const getSnapshot = (0, react.useCallback)(() => store.getSnapshot(), [store]);
2449
+ const snapshot = (0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
2450
+ const [notice, setNotice] = (0, react.useState)();
2451
+ return (0, react.createElement)("div", {
2452
+ "data-codingns-avatar-settings-editor": true,
2453
+ style: {
2454
+ display: "grid",
2455
+ gap: 10
2456
+ }
2457
+ }, (0, react.createElement)(AssistantAppearancePanel, {
2458
+ services,
2459
+ enabled,
2460
+ snapshot,
2461
+ notify: setNotice,
2462
+ ...previewProps
2463
+ }), notice?.kind !== "success" ? null : (0, react.createElement)("div", {
2464
+ role: "status",
2465
+ style: {
2466
+ fontSize: 13,
2467
+ color: require_theme.dshThemeColor.success
2468
+ }
2469
+ }, notice.message));
2470
+ }
2471
+ /** 保存只写 appearance 子字段,不覆盖其他窗口刚改过的语音或受管范围。 */
2472
+ function AssistantAppearancePanel({ services, enabled, snapshot, notify, ...previewProps }) {
2473
+ const t = require_transport.useCodingNsTranslator(services.locale);
2474
+ const manager = require_manager.getAssistantAvatarManager(services);
2475
+ const appearance = (0, react.useSyncExternalStore)(manager.subscribe, manager.getSnapshot, manager.getSnapshot);
2476
+ const selected = require_manager.selectedAssistantAvatar(appearance);
2477
+ const registry = require_manager.getAssistantAvatarRegistry(services);
2478
+ const adapters = (0, react.useSyncExternalStore)(manager.adapters.subscribe, manager.adapters.getSnapshot, manager.adapters.getSnapshot);
2479
+ const renderers = (0, react.useSyncExternalStore)(registry.subscribe, registry.getSnapshot, registry.getSnapshot);
2480
+ const [pending, setPending] = (0, react.useState)(false);
2481
+ const [name, setName] = (0, react.useState)("");
2482
+ const [source, setSource] = (0, react.useState)("");
2483
+ const [renderer, setRenderer] = (0, react.useState)("image");
2484
+ const [version, setVersion] = (0, react.useState)(2);
2485
+ const [error, setError] = (0, react.useState)("");
2486
+ const [manifestUrl, setManifestUrl] = (0, react.useState)("");
2487
+ const [adapterId, setAdapterId] = (0, react.useState)("auto");
2488
+ const [candidates, setCandidates] = (0, react.useState)([]);
2489
+ const [candidateUrl, setCandidateUrl] = (0, react.useState)("");
2490
+ const [addMode, setAddMode] = (0, react.useState)("package");
2491
+ const [customSize, setCustomSize] = (0, react.useState)(false);
2492
+ const importer = (0, react.useRef)();
2493
+ (0, react.useEffect)(() => () => importer.current?.abort(), [manager]);
2494
+ const selectedAdapterId = adapters.some((adapter) => adapter.id === adapterId) ? adapterId : "auto";
2495
+ const selectedRenderer = renderers.some((item) => item.id === renderer && item.id !== "builtin") ? renderer : "image";
2496
+ const disabled = !enabled || pending || snapshot.status !== "ready" || !snapshot.writable;
2497
+ const engine = useAssistantAvatarEngine({
2498
+ services,
2499
+ manager,
2500
+ appearance,
2501
+ disabled,
2502
+ notify
2503
+ });
2504
+ const save = async (action, message = t("avatar.saved")) => {
2505
+ if (disabled) return false;
2506
+ setPending(true);
2507
+ setError("");
2508
+ try {
2509
+ await action();
2510
+ if (!services.configurationDraft) notify({
2511
+ kind: "success",
2512
+ message
2513
+ });
2514
+ return true;
2515
+ } catch (failure) {
2516
+ if (failure instanceof Error && failure.name === "AbortError") {
2517
+ notify({
2518
+ kind: "info",
2519
+ message: t("avatar.cancelled")
2520
+ });
2521
+ return false;
2522
+ }
2523
+ const message = failure instanceof Error ? failure.message : String(failure);
2524
+ setError(message);
2525
+ notify({
2526
+ kind: "error",
2527
+ message
2528
+ });
2529
+ return false;
2530
+ } finally {
2531
+ setPending(false);
2532
+ }
2533
+ };
2534
+ const add = async () => {
2535
+ if (!require_manager.isAssistantAvatarSource(source.trim())) {
2536
+ setError(t("avatar.invalidSource"));
2537
+ return;
2538
+ }
2539
+ const model = {
2540
+ id: `avatar-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`,
2541
+ name: name.trim() || t("avatar.customName"),
2542
+ renderer: selectedRenderer,
2543
+ source: source.trim(),
2544
+ spriteVersion: version
2545
+ };
2546
+ if (selectedRenderer === "live2d" ? await engine.ensure(async () => {
2547
+ await manager.add(model);
2548
+ if (!services.configurationDraft) notify({
2549
+ kind: "success",
2550
+ message: t("avatar.saved")
2551
+ });
2552
+ }) : await save(() => manager.add(model))) {
2553
+ setName("");
2554
+ setSource("");
2555
+ }
2556
+ };
2557
+ const importPackage = async () => {
2558
+ if (!require_manager.isAssistantAvatarSource(manifestUrl.trim())) {
2559
+ setError(t("avatar.invalidSource"));
2560
+ return;
2561
+ }
2562
+ const controller = new AbortController();
2563
+ importer.current = controller;
2564
+ const accepted = await save(async () => {
2565
+ const discovered = await manager.discover(manifestUrl.trim(), controller.signal);
2566
+ setCandidates(discovered);
2567
+ setCandidateUrl(discovered[0]?.url ?? "");
2568
+ }, t("avatar.discovered"));
2569
+ if (importer.current === controller) importer.current = void 0;
2570
+ if (!accepted) {
2571
+ setCandidates([]);
2572
+ setCandidateUrl("");
2573
+ }
2574
+ };
2575
+ const installPackage = async (url) => {
2576
+ const controller = new AbortController();
2577
+ importer.current = controller;
2578
+ const accepted = await save(() => manager.installPackage(url, selectedAdapterId, controller.signal));
2579
+ if (importer.current === controller) importer.current = void 0;
2580
+ if (accepted) {
2581
+ setManifestUrl("");
2582
+ setCandidates([]);
2583
+ setCandidateUrl("");
2584
+ }
2585
+ };
2586
+ const field = (label, input) => (0, react.createElement)("label", { style: require_assistant_workbench.assistantSettingFieldStyle }, (0, react.createElement)("span", null, label), input);
2587
+ const toggle = (key, label) => (0, react.createElement)("label", { style: require_assistant_workbench.assistantSettingSwitchStyle }, (0, react.createElement)("span", null, label), (0, react.createElement)("input", {
2588
+ type: "checkbox",
2589
+ role: "switch",
2590
+ "aria-label": label,
2591
+ checked: appearance[key],
2592
+ disabled,
2593
+ onChange: (event) => {
2594
+ const checked = event.currentTarget.checked;
2595
+ save(() => manager.configure({ [key]: checked }));
2596
+ }
2597
+ }));
2598
+ const sizeField = (key, min, max, label) => field(label, (0, react.createElement)("input", {
2599
+ key: appearance[key],
2600
+ type: "number",
2601
+ min,
2602
+ max,
2603
+ step: 1,
2604
+ defaultValue: appearance[key],
2605
+ disabled,
2606
+ "aria-label": label,
2607
+ style: require_theme.dshSettingsFieldStyle,
2608
+ onBlur: (event) => {
2609
+ const value = Number(event.currentTarget.value);
2610
+ if (Number.isInteger(value) && value >= min && value <= max && value !== appearance[key]) save(() => manager.configure({ [key]: value }));
2611
+ else event.currentTarget.value = String(appearance[key]);
2612
+ }
2613
+ }));
2614
+ return (0, react.createElement)("section", {
2615
+ "aria-label": t("avatar.settingsTitle"),
2616
+ style: {
2617
+ ...require_assistant_workbench.assistantSettingTextStyle,
2618
+ display: "grid",
2619
+ gap: 12,
2620
+ borderTop: `1px solid ${require_theme.dshThemeColor.border}`,
2621
+ paddingTop: 14
2622
+ }
2623
+ }, (0, react.createElement)("strong", { style: { fontSize: 14 } }, t("avatar.settingsTitle")), (0, react.createElement)("p", { style: {
2624
+ ...require_theme.dshSettingsHelpStyle,
2625
+ margin: 0
2626
+ } }, t("avatar.settingsDescription")), (0, react.createElement)(AssistantAvatarCatalogPanel, {
2627
+ services,
2628
+ manager,
2629
+ appearance,
2630
+ disabled,
2631
+ notify,
2632
+ ...previewProps
2633
+ }), (0, react.createElement)("p", { style: {
2634
+ ...require_theme.dshSettingsHelpStyle,
2635
+ margin: 0
2636
+ } }, t("avatar.externalHint")), selected.renderer !== "live2d" ? null : (0, react.createElement)("p", { style: {
2637
+ ...require_theme.dshSettingsHelpStyle,
2638
+ margin: 0
2639
+ } }, t("avatar.live2dDependencyHint")), selected.package === void 0 ? null : (0, react.createElement)("div", {
2640
+ "data-codingns-avatar-package-info": true,
2641
+ style: {
2642
+ ...require_theme.dshSettingsHelpStyle,
2643
+ overflowWrap: "anywhere"
2644
+ }
2645
+ }, (0, react.createElement)("div", null, t("avatar.packageAuthor", { author: selected.package.author || t("avatar.notDeclared") })), (0, react.createElement)("div", null, t("avatar.packageLicense", { license: selected.package.license || t("avatar.notDeclared") })), selected.package.manifestUrl === void 0 ? null : (0, react.createElement)("a", {
2646
+ href: selected.package.manifestUrl,
2647
+ target: "_blank",
2648
+ rel: "noopener noreferrer"
2649
+ }, t("avatar.packageSource")), selected.package.homepage === void 0 ? null : (0, react.createElement)("div", null, (0, react.createElement)("a", {
2650
+ href: selected.package.homepage,
2651
+ target: "_blank",
2652
+ rel: "noopener noreferrer"
2653
+ }, t("avatar.packageHomepage")))), toggle("floatingEnabled", t("avatar.floatingEnabled")), (0, react.createElement)(AssistantFloatingSizeControl, {
2654
+ size: appearance.floatingSize,
2655
+ custom: customSize,
2656
+ disabled,
2657
+ t,
2658
+ onPreset: (preset) => {
2659
+ setCustomSize(preset === "custom");
2660
+ if (preset !== "custom") save(() => manager.configure({ floatingSize: preset === "mini" ? 72 : 144 }));
2661
+ },
2662
+ onSize: (floatingSize) => {
2663
+ save(() => manager.configure({ floatingSize }));
2664
+ }
2665
+ }), toggle("dialogEnabled", t("avatar.dialogEnabled")), sizeField("dialogSize", 120, 480, t("avatar.dialogSize")), require_manager.getBuiltinAssistantAvatar(appearance.selectedId) !== void 0 ? null : (0, react.createElement)("button", {
2666
+ type: "button",
2667
+ disabled,
2668
+ style: {
2669
+ ...require_theme.dshSettingsButtonStyle,
2670
+ justifySelf: "start"
2671
+ },
2672
+ onClick: () => {
2673
+ const id = appearance.selectedId;
2674
+ save(() => manager.remove(id));
2675
+ }
2676
+ }, t("avatar.remove")), selected.package?.manifestUrl === void 0 || selected.package.installationId !== void 0 ? null : (0, react.createElement)("button", {
2677
+ type: "button",
2678
+ disabled,
2679
+ style: {
2680
+ ...require_theme.dshSettingsButtonStyle,
2681
+ justifySelf: "start"
2682
+ },
2683
+ onClick: () => {
2684
+ installPackage(selected.package.manifestUrl);
2685
+ }
2686
+ }, t("avatar.installCurrent")), (0, react.createElement)("details", { "data-codingns-avatar-add": true }, (0, react.createElement)("summary", { style: {
2687
+ cursor: "pointer",
2688
+ fontSize: 13
2689
+ } }, t("avatar.add")), field(t("avatar.addMode"), (0, react.createElement)("select", {
2690
+ value: addMode,
2691
+ disabled,
2692
+ style: {
2693
+ ...require_theme.dshSettingsFieldStyle,
2694
+ marginTop: 12
2695
+ },
2696
+ "data-codingns-avatar-add-mode": true,
2697
+ onChange: (event) => setAddMode(event.currentTarget.value === "custom" ? "custom" : "package")
2698
+ }, (0, react.createElement)("option", { value: "package" }, t("avatar.importPackage")), (0, react.createElement)("option", { value: "custom" }, t("avatar.customMaterial")))), addMode !== "package" ? null : (0, react.createElement)("div", {
2699
+ "data-codingns-avatar-package-import": true,
2700
+ style: {
2701
+ display: "grid",
2702
+ gap: 12,
2703
+ marginTop: 12
2704
+ }
2705
+ }, field(t("avatar.adapter"), (0, react.createElement)("select", {
2706
+ value: selectedAdapterId,
2707
+ disabled,
2708
+ style: require_theme.dshSettingsFieldStyle,
2709
+ onChange: (event) => setAdapterId(event.currentTarget.value)
2710
+ }, (0, react.createElement)("option", { value: "auto" }, t("avatar.adapter.auto")), ...adapters.map((adapter) => (0, react.createElement)("option", {
2711
+ key: adapter.id,
2712
+ value: adapter.id
2713
+ }, adapter.labelKey === void 0 ? adapter.name ?? adapter.id : t(adapter.labelKey))))), field(t("avatar.manifestUrl"), (0, react.createElement)("input", {
2714
+ value: manifestUrl,
2715
+ disabled,
2716
+ maxLength: 2048,
2717
+ style: require_theme.dshSettingsFieldStyle,
2718
+ onChange: (event) => {
2719
+ setManifestUrl(event.currentTarget.value);
2720
+ setCandidates([]);
2721
+ setCandidateUrl("");
2722
+ }
2723
+ })), (0, react.createElement)("p", { style: {
2724
+ ...require_theme.dshSettingsHelpStyle,
2725
+ margin: 0
2726
+ } }, t("avatar.importHint")), (0, react.createElement)("button", {
2727
+ type: "button",
2728
+ disabled,
2729
+ style: {
2730
+ ...require_theme.dshSettingsButtonStyle,
2731
+ justifySelf: "start"
2732
+ },
2733
+ onClick: () => {
2734
+ importPackage();
2735
+ }
2736
+ }, pending && importer.current !== void 0 ? t("avatar.importing") : t("avatar.discover")), candidates.length === 0 ? null : field(t("avatar.installCandidate"), (0, react.createElement)("select", {
2737
+ value: candidateUrl,
2738
+ disabled,
2739
+ style: require_theme.dshSettingsFieldStyle,
2740
+ onChange: (event) => setCandidateUrl(event.currentTarget.value)
2741
+ }, ...candidates.map((candidate) => (0, react.createElement)("option", {
2742
+ key: candidate.url,
2743
+ value: candidate.url
2744
+ }, candidate.name)))), candidates.length === 0 ? null : (0, react.createElement)("button", {
2745
+ type: "button",
2746
+ disabled: disabled || appearance.models.length >= 21,
2747
+ style: {
2748
+ ...require_theme.dshSettingsButtonStyle,
2749
+ justifySelf: "start"
2750
+ },
2751
+ onClick: () => {
2752
+ installPackage(candidateUrl);
2753
+ }
2754
+ }, t("avatar.importUse")), !pending || importer.current === void 0 ? null : (0, react.createElement)("div", {
2755
+ role: "status",
2756
+ style: {
2757
+ display: "grid",
2758
+ gap: 8
2759
+ }
2760
+ }, (0, react.createElement)("span", null, t("avatar.installProgress")), (0, react.createElement)("progress", {
2761
+ "aria-label": t("avatar.installProgress"),
2762
+ style: { width: "100%" }
2763
+ }), (0, react.createElement)("button", {
2764
+ type: "button",
2765
+ style: {
2766
+ ...require_theme.dshSettingsButtonStyle,
2767
+ justifySelf: "start"
2768
+ },
2769
+ onClick: () => importer.current?.abort()
2770
+ }, t("avatar.cancel")))), addMode !== "custom" ? null : (0, react.createElement)("div", {
2771
+ "data-codingns-avatar-custom-add": true,
2772
+ style: {
2773
+ display: "grid",
2774
+ gap: 12,
2775
+ marginTop: 12
2776
+ }
2777
+ }, field(t("avatar.name"), (0, react.createElement)("input", {
2778
+ value: name,
2779
+ disabled,
2780
+ maxLength: 80,
2781
+ style: require_theme.dshSettingsFieldStyle,
2782
+ onChange: (event) => setName(event.currentTarget.value)
2783
+ })), field(t("avatar.type"), (0, react.createElement)("select", {
2784
+ value: selectedRenderer,
2785
+ disabled,
2786
+ style: require_theme.dshSettingsFieldStyle,
2787
+ onChange: (event) => setRenderer(event.currentTarget.value)
2788
+ }, ...renderers.filter((item) => item.id !== "builtin").map((item) => (0, react.createElement)("option", {
2789
+ key: item.id,
2790
+ value: item.id
2791
+ }, item.labelKey === void 0 ? item.name ?? item.id : t(item.labelKey))))), field(t("avatar.source"), (0, react.createElement)("input", {
2792
+ value: source,
2793
+ disabled,
2794
+ maxLength: 2048,
2795
+ type: "text",
2796
+ style: require_theme.dshSettingsFieldStyle,
2797
+ onChange: (event) => setSource(event.currentTarget.value)
2798
+ })), (0, react.createElement)("p", { style: {
2799
+ ...require_theme.dshSettingsHelpStyle,
2800
+ margin: 0
2801
+ } }, t([
2802
+ "image",
2803
+ "spritesheet",
2804
+ "live2d"
2805
+ ].includes(selectedRenderer) ? `avatar.sourceHint.${selectedRenderer}` : "avatar.sourceHint.custom")), selectedRenderer !== "live2d" ? null : (0, react.createElement)("p", { style: {
2806
+ ...require_theme.dshSettingsHelpStyle,
2807
+ margin: 0
2808
+ } }, t("avatar.live2dDependencyHint")), selectedRenderer !== "spritesheet" ? null : field(t("avatar.spriteVersion"), (0, react.createElement)("select", {
2809
+ value: version,
2810
+ disabled,
2811
+ style: require_theme.dshSettingsFieldStyle,
2812
+ onChange: (event) => setVersion(event.currentTarget.value === "1" ? 1 : 2)
2813
+ }, (0, react.createElement)("option", { value: 1 }, "v1 · 1536×1872"), (0, react.createElement)("option", { value: 2 }, "v2 · 1536×2288"))), (0, react.createElement)("button", {
2814
+ type: "button",
2815
+ disabled: disabled || appearance.models.length >= 21,
2816
+ onClick: () => {
2817
+ add();
2818
+ },
2819
+ style: {
2820
+ ...require_theme.dshSettingsButtonStyle,
2821
+ justifySelf: "start"
2822
+ }
2823
+ }, t("avatar.addUse")))), (0, react.createElement)(AssistantAvatarEngineProgress, {
2824
+ controller: engine,
2825
+ t
2826
+ }), error === "" ? null : (0, react.createElement)("div", {
2827
+ role: "alert",
2828
+ style: {
2829
+ color: require_theme.dshThemeColor.error,
2830
+ fontSize: 13
2831
+ }
2832
+ }, error), (0, react.createElement)(AssistantAvatarEngineDialog, {
2833
+ controller: engine,
2834
+ t
2835
+ }));
2836
+ }
2837
+ /** 以数值判断旧配置的档位;选择自定义时才展开输入框,不增加持久字段。 */
2838
+ function AssistantFloatingSizeControl({ size, custom, disabled, t, onPreset, onSize }) {
2839
+ const preset = custom ? "custom" : size === 72 ? "mini" : size === 144 ? "standard" : "custom";
2840
+ const label = t("avatar.floatingSize");
2841
+ return (0, react.createElement)("div", {
2842
+ "data-codingns-avatar-floating-size": true,
2843
+ style: {
2844
+ display: "grid",
2845
+ gap: 8
2846
+ }
2847
+ }, (0, react.createElement)("label", { style: require_assistant_workbench.assistantSettingFieldStyle }, label, (0, react.createElement)("select", {
2848
+ value: preset,
2849
+ disabled,
2850
+ "aria-label": label,
2851
+ style: require_theme.dshSettingsFieldStyle,
2852
+ onChange: (event) => {
2853
+ const next = event.currentTarget.value;
2854
+ if (next === "mini" || next === "standard" || next === "custom") onPreset(next);
2855
+ }
2856
+ }, (0, react.createElement)("option", { value: "mini" }, t("avatar.size.mini")), (0, react.createElement)("option", { value: "standard" }, t("avatar.size.standard")), (0, react.createElement)("option", { value: "custom" }, t("avatar.size.custom")))), preset !== "custom" ? null : (0, react.createElement)("label", { style: require_assistant_workbench.assistantSettingFieldStyle }, t("avatar.customSize"), (0, react.createElement)("input", {
2857
+ key: size,
2858
+ type: "number",
2859
+ min: 72,
2860
+ max: 320,
2861
+ step: 1,
2862
+ defaultValue: size,
2863
+ disabled,
2864
+ "aria-label": t("avatar.customSize"),
2865
+ style: require_theme.dshSettingsFieldStyle,
2866
+ onBlur: (event) => {
2867
+ const value = Number(event.currentTarget.value);
2868
+ if (!disabled && Number.isInteger(value) && value >= 72 && value <= 320 && value !== size) onSize(value);
2869
+ else event.currentTarget.value = String(size);
2870
+ }
2871
+ })));
2872
+ }
2873
+ //#endregion
2874
+ //#region src/client/features/assistant-voice-settings.ts
2875
+ /** 设置页和对话窗口共用独立音色编辑器,不与形象包或识别模型混合。 */
2876
+ function AssistantVoiceSettings({ services, enabled = true, active = true, embedded = false }) {
2877
+ const t = require_transport.useCodingNsTranslator(services.locale);
2878
+ const [snapshot, setSnapshot] = (0, react.useState)();
2879
+ const [selectedId, setSelectedId] = (0, react.useState)("moss:Junhao");
2880
+ const [parameters, setParameters] = (0, react.useState)({ ...require_manager.DEFAULT_ASSISTANT_TTS_PARAMETERS });
2881
+ const [siteId, setSiteId] = (0, react.useState)("kyutai");
2882
+ const [source, setSource] = (0, react.useState)("");
2883
+ const [name, setName] = (0, react.useState)("");
2884
+ const [language, setLanguage] = (0, react.useState)("unknown");
2885
+ const [gender, setGender] = (0, react.useState)("unknown");
2886
+ const [busy, setBusy] = (0, react.useState)();
2887
+ const [error, setError] = (0, react.useState)("");
2888
+ const [writable, setWritable] = (0, react.useState)(() => services.settings.getSnapshot().writable);
2889
+ const alive = (0, react.useRef)(false);
2890
+ const pending = (0, react.useRef)(false);
2891
+ const reading = (0, react.useRef)(false);
2892
+ const abort = (0, react.useRef)();
2893
+ const player = (0, react.useRef)();
2894
+ if (player.current === void 0) player.current = new require_react_dom$1.MossVoiceOutput();
2895
+ (0, react.useEffect)(() => {
2896
+ if (!active) player.current?.cancel();
2897
+ }, [active]);
2898
+ const voices = snapshot?.voices ?? require_manager.MOSS_BUILTIN_VOICES;
2899
+ const selected = voices.find((voice) => voice.id === selectedId) ?? voices[0];
2900
+ const site = require_manager.ASSISTANT_VOICE_SITES.find((site) => site.id === siteId) ?? require_manager.ASSISTANT_VOICE_SITES[0];
2901
+ const disabled = !enabled || !writable || busy !== void 0 || snapshot?.status.busy === true;
2902
+ const ready = snapshot?.status.ready === true;
2903
+ const backend = snapshot?.settings.backend ?? services.settings.getSnapshot().value?.assistant?.tts?.backend ?? "browser";
2904
+ const moss = backend === "moss-onnx";
2905
+ const refresh = (0, react.useCallback)(async () => {
2906
+ if (reading.current) return;
2907
+ reading.current = true;
2908
+ try {
2909
+ const result = await services.rpc.call(require_transport.CODINGNS_RPC_CHANNEL, "assistant/tts/catalog", {}, abort.current?.signal);
2910
+ if (!result.ok) throw new Error(result.error.message);
2911
+ if (alive.current) setSnapshot(result.value);
2912
+ } catch (error) {
2913
+ if (alive.current) setError(message$2(error));
2914
+ } finally {
2915
+ reading.current = false;
2916
+ }
2917
+ }, [services.rpc]);
2918
+ (0, react.useEffect)(() => {
2919
+ alive.current = true;
2920
+ abort.current = new AbortController();
2921
+ refresh();
2922
+ const changed = () => {
2923
+ setWritable(services.settings.getSnapshot().writable);
2924
+ refresh();
2925
+ };
2926
+ const dispose = services.settings.subscribe(changed);
2927
+ window.addEventListener("focus", changed);
2928
+ return () => {
2929
+ alive.current = false;
2930
+ abort.current?.abort();
2931
+ player.current?.dispose();
2932
+ dispose();
2933
+ window.removeEventListener("focus", changed);
2934
+ };
2935
+ }, [refresh, services.settings]);
2936
+ (0, react.useEffect)(() => {
2937
+ if (snapshot !== void 0) setSelectedId(snapshot.settings.selectedId);
2938
+ }, [snapshot?.settings.selectedId]);
2939
+ (0, react.useEffect)(() => {
2940
+ if (snapshot !== void 0) setParameters(services.configurationDraft ? {
2941
+ ...require_manager.DEFAULT_ASSISTANT_TTS_PARAMETERS,
2942
+ ...snapshot.settings.parameters
2943
+ } : require_manager.readAssistantTtsParameters(snapshot.settings.parameters));
2944
+ }, [
2945
+ snapshot?.settings.parameters?.rate,
2946
+ snapshot?.settings.parameters?.volume,
2947
+ snapshot?.settings.parameters?.segmentPauseMs,
2948
+ snapshot?.settings.parameters?.chunkTokens,
2949
+ snapshot?.settings.parameters?.seed
2950
+ ]);
2951
+ (0, react.useEffect)(() => {
2952
+ if (busy !== "setup" && snapshot?.status.busy !== true) return void 0;
2953
+ const timer = setInterval(() => {
2954
+ refresh();
2955
+ }, 1e3);
2956
+ return () => clearInterval(timer);
2957
+ }, [
2958
+ busy,
2959
+ snapshot?.status.busy,
2960
+ refresh
2961
+ ]);
2962
+ (0, react.useEffect)(() => {
2963
+ if (!enabled || !moss) player.current?.cancel();
2964
+ }, [enabled, moss]);
2965
+ const run = async (action, value) => {
2966
+ if (pending.current || disabled) return;
2967
+ pending.current = true;
2968
+ setBusy(action);
2969
+ setError("");
2970
+ player.current?.cancel();
2971
+ try {
2972
+ const result = await services.rpc.call(require_transport.CODINGNS_RPC_CHANNEL, `assistant/tts/${action}`, value, abort.current?.signal);
2973
+ if (!result.ok) throw new Error(result.error.message);
2974
+ if (alive.current) {
2975
+ setSnapshot(result.value);
2976
+ if (action === "import") {
2977
+ setSource("");
2978
+ setName("");
2979
+ }
2980
+ }
2981
+ if (services.settings.reload !== void 0) await services.settings.reload();
2982
+ else await services.settings.load?.();
2983
+ } catch (error) {
2984
+ if (alive.current) setError(message$2(error));
2985
+ } finally {
2986
+ pending.current = false;
2987
+ if (alive.current) {
2988
+ setBusy(void 0);
2989
+ await refresh();
2990
+ }
2991
+ }
2992
+ };
2993
+ const preview = async () => {
2994
+ if (pending.current || disabled || !active || !moss || !ready) return;
2995
+ pending.current = true;
2996
+ setBusy("preview");
2997
+ setError("");
2998
+ try {
2999
+ await player.current.speak(t("tts.defaultText"), selected.id, void 0, require_manager.validateAssistantTtsParameters(parameters));
3000
+ } catch (error) {
3001
+ if (alive.current) setError(message$2(error));
3002
+ } finally {
3003
+ pending.current = false;
3004
+ if (alive.current) setBusy(void 0);
3005
+ }
3006
+ };
3007
+ const button = (label, action, locked = false) => (0, react.createElement)("button", {
3008
+ type: "button",
3009
+ disabled: disabled || locked,
3010
+ style: require_theme.dshSettingsButtonStyle,
3011
+ onClick: action
3012
+ }, label);
3013
+ const field = (label, content) => (0, react.createElement)("label", { style: require_assistant_workbench.assistantSettingFieldStyle }, (0, react.createElement)("span", null, label), content);
3014
+ const input = (value, change, maxLength = 2048) => (0, react.createElement)("input", {
3015
+ value,
3016
+ disabled,
3017
+ maxLength,
3018
+ style: require_theme.dshSettingsFieldStyle,
3019
+ onChange: (event) => change(event.currentTarget.value)
3020
+ });
3021
+ const options = (value, change, values) => (0, react.createElement)("select", {
3022
+ value,
3023
+ disabled,
3024
+ style: require_theme.dshSettingsFieldStyle,
3025
+ onChange: (event) => change(event.currentTarget.value)
3026
+ }, ...values.map((item) => (0, react.createElement)("option", {
3027
+ key: item,
3028
+ value: item
3029
+ }, t(`tts.${item}`))));
3030
+ const number = (key, scale = 1, slider = false) => {
3031
+ const limits = require_manager.ASSISTANT_TTS_PARAMETER_LIMITS[key];
3032
+ const value = parameters[key];
3033
+ const change = (event) => {
3034
+ const raw = event.currentTarget.value;
3035
+ const next = {
3036
+ ...parameters,
3037
+ [key]: raw === "" ? key === "seed" ? null : NaN : Number(raw) / scale
3038
+ };
3039
+ setParameters(next);
3040
+ if (services.configurationDraft) services.settings.mutate([{
3041
+ op: "set",
3042
+ path: [
3043
+ "assistant",
3044
+ "tts",
3045
+ "parameters"
3046
+ ],
3047
+ value: next
3048
+ }]).catch((cause) => setError(message$2(cause)));
3049
+ };
3050
+ const props = {
3051
+ disabled,
3052
+ min: limits.min * scale,
3053
+ max: limits.max * scale,
3054
+ step: limits.step * scale,
3055
+ onChange: change
3056
+ };
3057
+ return field(t(`tts.${key}`), (0, react.createElement)("div", { style: {
3058
+ display: "grid",
3059
+ gridTemplateColumns: slider ? "minmax(0, 1fr) 90px" : "1fr",
3060
+ gap: 10,
3061
+ alignItems: "center"
3062
+ } }, slider ? (0, react.createElement)("input", {
3063
+ ...props,
3064
+ type: "range",
3065
+ "aria-label": t(`tts.${key}`),
3066
+ value: value === null || !Number.isFinite(value) ? require_manager.DEFAULT_ASSISTANT_TTS_PARAMETERS[key] * scale : Number((value * scale).toFixed(4))
3067
+ }) : null, (0, react.createElement)("input", {
3068
+ ...props,
3069
+ type: "number",
3070
+ value: value === null || !Number.isFinite(value) ? "" : Number((value * scale).toFixed(4)),
3071
+ placeholder: key === "seed" ? t("tts.autoSeed") : void 0,
3072
+ "aria-label": t(`tts.${key}`),
3073
+ style: require_theme.dshSettingsFieldStyle,
3074
+ "data-codingns-tts-parameter": key
3075
+ })));
3076
+ };
3077
+ return (0, react.createElement)("section", {
3078
+ "aria-label": t("tts.title"),
3079
+ "data-codingns-voice-settings": true,
3080
+ style: {
3081
+ ...require_assistant_workbench.assistantSettingTextStyle,
3082
+ display: "grid",
3083
+ gap: 12,
3084
+ ...embedded ? {} : {
3085
+ borderTop: `1px solid ${require_theme.dshThemeColor.border}`,
3086
+ paddingTop: 14
3087
+ }
3088
+ }
3089
+ }, (0, react.createElement)("strong", null, t("tts.title")), moss ? (0, react.createElement)("p", { style: {
3090
+ ...require_theme.dshSettingsHelpStyle,
3091
+ margin: 0
3092
+ } }, t("tts.description")) : null, field(t("tts.backend"), (0, react.createElement)("select", {
3093
+ value: backend,
3094
+ disabled: disabled || snapshot === void 0,
3095
+ style: require_theme.dshSettingsFieldStyle,
3096
+ onChange: (event) => {
3097
+ run("select", {
3098
+ id: selectedId,
3099
+ backend: event.currentTarget.value
3100
+ });
3101
+ }
3102
+ }, (0, react.createElement)("option", { value: "browser" }, t("tts.browser")), (0, react.createElement)("option", {
3103
+ value: "moss-onnx",
3104
+ disabled: !ready
3105
+ }, t("tts.moss")))), (0, react.createElement)("div", { style: {
3106
+ display: "flex",
3107
+ flexWrap: "wrap",
3108
+ gap: 8
3109
+ } }, button(t(services.configurationDraft ? "tts.prepare" : "tts.setup"), () => {
3110
+ run("setup", {});
3111
+ }), moss ? button(t("tts.refresh"), () => {
3112
+ refresh();
3113
+ }) : null), !moss && !snapshot?.status.busy && busy !== "setup" ? null : (0, react.createElement)("div", {
3114
+ role: "status",
3115
+ style: require_theme.dshSettingsHelpStyle
3116
+ }, snapshot?.status.busy ? snapshot.status.phase : busy === "setup" ? t("tts.busy") : ready ? t("tts.ready") : t("tts.notReady")), snapshot?.status.busy ? (0, react.createElement)("progress", {
3117
+ max: snapshot.status.totalBytes ?? void 0,
3118
+ value: snapshot.status.totalBytes === null ? void 0 : snapshot.status.downloadedBytes,
3119
+ style: { width: "100%" },
3120
+ "aria-label": snapshot.status.phase
3121
+ }) : null, moss ? (0, react.createElement)("div", {
3122
+ "data-codingns-moss-voices": true,
3123
+ style: {
3124
+ display: "grid",
3125
+ gap: 12
3126
+ }
3127
+ }, field(t("tts.selected"), (0, react.createElement)("select", {
3128
+ value: selectedId,
3129
+ disabled,
3130
+ style: require_theme.dshSettingsFieldStyle,
3131
+ "data-codingns-voice-list": true,
3132
+ onChange: (event) => {
3133
+ const id = event.currentTarget.value;
3134
+ player.current?.cancel();
3135
+ setSelectedId(id);
3136
+ if (services.configurationDraft) services.settings.mutate([{
3137
+ op: "set",
3138
+ path: [
3139
+ "assistant",
3140
+ "tts",
3141
+ "selectedId"
3142
+ ],
3143
+ value: id
3144
+ }]).catch((cause) => setError(message$2(cause)));
3145
+ }
3146
+ }, ...voices.map((voice) => (0, react.createElement)("option", {
3147
+ key: voice.id,
3148
+ value: voice.id
3149
+ }, voiceLabel(voice, t))))), (0, react.createElement)("div", { style: {
3150
+ ...require_theme.dshSettingsHelpStyle,
3151
+ overflowWrap: "anywhere"
3152
+ } }, t("tts.license", { license: selected.license }), " · ", (0, react.createElement)("a", {
3153
+ href: selected.source,
3154
+ target: "_blank",
3155
+ rel: "noopener noreferrer"
3156
+ }, t("tts.sourceLink"))), (0, react.createElement)("div", { style: {
3157
+ display: "flex",
3158
+ flexWrap: "wrap",
3159
+ gap: 8
3160
+ } }, services.configurationDraft ? null : button(t("tts.apply"), () => {
3161
+ run("select", { id: selectedId });
3162
+ }, !ready), button(t("tts.preview"), () => {
3163
+ preview();
3164
+ }, !ready), busy !== "preview" ? null : (0, react.createElement)("button", {
3165
+ type: "button",
3166
+ style: require_theme.dshSettingsButtonStyle,
3167
+ onClick: () => player.current?.cancel()
3168
+ }, t("tts.stop")), selected.kind !== "reference" ? null : button(t("tts.remove"), () => {
3169
+ run("remove", { id: selected.id });
3170
+ }))) : null, (0, react.createElement)("div", {
3171
+ "data-codingns-tts-controls": true,
3172
+ style: {
3173
+ display: "grid",
3174
+ gap: 12
3175
+ }
3176
+ }, (0, react.createElement)("strong", null, t("tts.playbackSettings")), number("rate", 1, true), number("volume", 100, true), moss ? (0, react.createElement)("p", { style: {
3177
+ ...require_theme.dshSettingsHelpStyle,
3178
+ margin: 0
3179
+ } }, t("tts.rateHint")) : null, moss ? (0, react.createElement)("details", null, (0, react.createElement)("summary", { style: { cursor: "pointer" } }, t("tts.advanced")), (0, react.createElement)("div", { style: {
3180
+ display: "grid",
3181
+ gap: 12,
3182
+ marginTop: 12
3183
+ } }, (0, react.createElement)("p", { style: {
3184
+ ...require_theme.dshSettingsHelpStyle,
3185
+ margin: 0
3186
+ } }, t("tts.advancedHint")), number("segmentPauseMs"), number("chunkTokens"), number("seed"), (0, react.createElement)("p", { style: {
3187
+ ...require_theme.dshSettingsHelpStyle,
3188
+ margin: 0
3189
+ } }, t("tts.seedHint")))) : null, moss ? (0, react.createElement)("p", { style: {
3190
+ ...require_theme.dshSettingsHelpStyle,
3191
+ margin: 0
3192
+ } }, t("tts.parametersHint")) : null, (0, react.createElement)("div", { style: {
3193
+ display: "flex",
3194
+ flexWrap: "wrap",
3195
+ gap: 8
3196
+ } }, services.configurationDraft ? null : button(t("tts.saveParameters"), () => {
3197
+ run("configure", { parameters });
3198
+ }, snapshot === void 0), button(t(services.configurationDraft ? "tts.resetDraftParameters" : "tts.resetParameters"), () => {
3199
+ const next = moss ? { ...require_manager.DEFAULT_ASSISTANT_TTS_PARAMETERS } : {
3200
+ ...parameters,
3201
+ rate: require_manager.DEFAULT_ASSISTANT_TTS_PARAMETERS.rate,
3202
+ volume: require_manager.DEFAULT_ASSISTANT_TTS_PARAMETERS.volume
3203
+ };
3204
+ setParameters(next);
3205
+ if (services.configurationDraft) services.settings.mutate([{
3206
+ op: "set",
3207
+ path: [
3208
+ "assistant",
3209
+ "tts",
3210
+ "parameters"
3211
+ ],
3212
+ value: next
3213
+ }]).catch((cause) => setError(message$2(cause)));
3214
+ else run("configure", { parameters: next });
3215
+ }, snapshot === void 0))), moss ? (0, react.createElement)("details", { "data-codingns-voice-import": true }, (0, react.createElement)("summary", { style: { cursor: "pointer" } }, t(services.configurationDraft ? "tts.importDraft" : "tts.import")), (0, react.createElement)("div", { style: {
3216
+ display: "grid",
3217
+ gap: 12,
3218
+ marginTop: 12
3219
+ } }, field(t("tts.site"), (0, react.createElement)("select", {
3220
+ value: siteId,
3221
+ disabled,
3222
+ style: require_theme.dshSettingsFieldStyle,
3223
+ onChange: (event) => setSiteId(event.currentTarget.value)
3224
+ }, ...require_manager.ASSISTANT_VOICE_SITES.map((site) => (0, react.createElement)("option", {
3225
+ key: site.id,
3226
+ value: site.id
3227
+ }, site.name)))), (0, react.createElement)("a", {
3228
+ href: site.url,
3229
+ target: "_blank",
3230
+ rel: "noopener noreferrer",
3231
+ "aria-disabled": !enabled,
3232
+ onClick: (event) => {
3233
+ if (!enabled) event.preventDefault();
3234
+ },
3235
+ style: {
3236
+ ...require_theme.dshSettingsButtonStyle,
3237
+ justifySelf: "start",
3238
+ textDecoration: "none"
3239
+ }
3240
+ }, t("tts.openSite")), (0, react.createElement)("a", {
3241
+ href: siteId === "aishell" ? "https://huggingface.co/datasets/AISHELL/AISHELL-3/tree/main/train/wav" : "https://huggingface.co/kyutai/tts-voices/tree/main/voice-donations",
3242
+ target: "_blank",
3243
+ rel: "noopener noreferrer",
3244
+ "aria-disabled": !enabled,
3245
+ onClick: (event) => {
3246
+ if (!enabled) event.preventDefault();
3247
+ }
3248
+ }, t("tts.openRecordings")), (0, react.createElement)("p", { style: {
3249
+ ...require_theme.dshSettingsHelpStyle,
3250
+ margin: 0
3251
+ } }, t(siteId === "aishell" ? "tts.aishellHint" : "tts.kyutaiHint")), field(t("tts.source"), input(source, setSource)), field(t("tts.name"), input(name, setName, 80)), field(t("tts.language"), options(language, setLanguage, [
3252
+ "unknown",
3253
+ "zh",
3254
+ "en",
3255
+ "ja",
3256
+ "fr"
3257
+ ])), field(t("tts.gender"), options(gender, setGender, [
3258
+ "unknown",
3259
+ "male",
3260
+ "female"
3261
+ ])), (0, react.createElement)("p", { style: {
3262
+ ...require_theme.dshSettingsHelpStyle,
3263
+ margin: 0
3264
+ } }, t("tts.sourceHint")), button(t(services.configurationDraft ? "tts.importDraft" : "tts.import"), () => {
3265
+ const reference = siteId === "aishell" && !source.includes(":") ? `aishell:${source}` : source;
3266
+ run("import", {
3267
+ source: reference,
3268
+ name,
3269
+ language,
3270
+ gender
3271
+ });
3272
+ }, !ready || source.trim() === ""))) : null, error || snapshot?.status.error ? (0, react.createElement)("div", {
3273
+ role: "alert",
3274
+ style: {
3275
+ color: require_theme.dshThemeColor.error,
3276
+ fontSize: 13,
3277
+ overflowWrap: "anywhere"
3278
+ }
3279
+ }, error || snapshot?.status.error) : null);
3280
+ }
3281
+ function voiceLabel(voice, t) {
3282
+ const language = [
3283
+ "zh",
3284
+ "en",
3285
+ "ja",
3286
+ "fr",
3287
+ "unknown"
3288
+ ].includes(voice.language) ? t(`tts.${voice.language}`) : voice.language;
3289
+ return `${voice.name} · ${language} · ${t(`tts.${voice.gender}`)}`;
3290
+ }
3291
+ function message$2(error) {
3292
+ return error instanceof Error ? error.message : String(error);
3293
+ }
3294
+ //#endregion
3295
+ //#region src/client/features/assistant-voice-settings-group.ts
3296
+ /** 两组独立收起,保留子组件实例,避免关闭卡片丢失草稿或中断下载。 */
3297
+ function AssistantVoiceSettingsGroup(props) {
3298
+ const [expanded, setExpanded] = (0, react.useState)(false);
3299
+ return (0, react.createElement)(AssistantVoiceSettingsGroupView, {
3300
+ ...props,
3301
+ expanded,
3302
+ onToggle: setExpanded
3303
+ });
3304
+ }
3305
+ /** 原生折叠语义支持键盘操作;内容保留,隐藏后通过 active 停止试听和闲置查询。 */
3306
+ function AssistantVoiceSettingsGroupView({ kind, hint, active, disabled, expanded, t, renderContent, onToggle }) {
3307
+ return (0, react.createElement)("details", {
3308
+ open: expanded,
3309
+ "data-codingns-voice-settings-group": kind,
3310
+ onToggle: (event) => onToggle(event.currentTarget.open),
3311
+ style: {
3312
+ minWidth: 0,
3313
+ border: `1px solid ${require_theme.dshThemeColor.border}`,
3314
+ borderRadius: 14,
3315
+ color: require_theme.dshThemeColor.labelPrimary,
3316
+ background: require_theme.dshThemeColor.cardBackground
3317
+ }
3318
+ }, (0, react.createElement)("summary", { style: {
3319
+ display: "flex",
3320
+ alignItems: "center",
3321
+ gap: 12,
3322
+ padding: "16px 18px",
3323
+ minWidth: 0,
3324
+ cursor: "pointer",
3325
+ listStyle: "none",
3326
+ borderRadius: expanded ? "14px 14px 0 0" : 14
3327
+ } }, (0, react.createElement)("span", {
3328
+ "aria-hidden": true,
3329
+ style: {
3330
+ width: 36,
3331
+ height: 36,
3332
+ flexShrink: 0,
3333
+ display: "flex",
3334
+ alignItems: "center",
3335
+ justifyContent: "center",
3336
+ borderRadius: 10,
3337
+ color: require_theme.dshThemeColor.accent,
3338
+ background: require_theme.dshThemeColor.surfaceSubtle
3339
+ }
3340
+ }, groupIcon(kind)), (0, react.createElement)("span", { style: {
3341
+ display: "grid",
3342
+ gap: 4,
3343
+ flex: "1 1 auto",
3344
+ minWidth: 0
3345
+ } }, (0, react.createElement)("strong", { style: {
3346
+ fontSize: 14,
3347
+ fontWeight: 600,
3348
+ lineHeight: 1.4
3349
+ } }, t(`voice.settings.${kind}`)), (0, react.createElement)("span", {
3350
+ title: hint,
3351
+ style: {
3352
+ fontSize: 12,
3353
+ lineHeight: 1.5,
3354
+ color: require_theme.dshThemeColor.labelSecondary,
3355
+ whiteSpace: "nowrap",
3356
+ overflow: "hidden",
3357
+ textOverflow: "ellipsis"
3358
+ }
3359
+ }, hint)), (0, react.createElement)("svg", {
3360
+ "aria-hidden": true,
3361
+ width: 18,
3362
+ height: 18,
3363
+ viewBox: "0 0 24 24",
3364
+ fill: "none",
3365
+ stroke: "currentColor",
3366
+ strokeWidth: 1.8,
3367
+ style: {
3368
+ flexShrink: 0,
3369
+ color: require_theme.dshThemeColor.labelSecondary,
3370
+ transform: expanded ? "rotate(180deg)" : "none"
3371
+ }
3372
+ }, (0, react.createElement)("path", {
3373
+ d: "m6 9 6 6 6-6",
3374
+ strokeLinecap: "round",
3375
+ strokeLinejoin: "round"
3376
+ }))), (0, react.createElement)("fieldset", {
3377
+ disabled,
3378
+ style: {
3379
+ display: "grid",
3380
+ gap: 18,
3381
+ margin: 0,
3382
+ padding: 18,
3383
+ minWidth: 0,
3384
+ border: 0,
3385
+ borderTop: `1px solid ${require_theme.dshThemeColor.border}`
3386
+ }
3387
+ }, (0, react.createElement)("style", null, groupCss), renderContent(active && expanded)));
3388
+ }
3389
+ function groupIcon(kind) {
3390
+ return (0, react.createElement)("svg", {
3391
+ width: 20,
3392
+ height: 20,
3393
+ viewBox: "0 0 24 24",
3394
+ fill: "none",
3395
+ stroke: "currentColor",
3396
+ strokeWidth: 1.6,
3397
+ strokeLinecap: "round",
3398
+ strokeLinejoin: "round"
3399
+ }, kind === "input" ? [(0, react.createElement)("rect", {
3400
+ key: "mic",
3401
+ x: 9,
3402
+ y: 2,
3403
+ width: 6,
3404
+ height: 12,
3405
+ rx: 3
3406
+ }), (0, react.createElement)("path", {
3407
+ key: "stand",
3408
+ d: "M5 10v2a7 7 0 0 0 14 0v-2M12 19v3M8 22h8"
3409
+ })] : [(0, react.createElement)("path", {
3410
+ key: "speaker",
3411
+ d: "m11 4-6 5H2v6h3l6 5V4Z"
3412
+ }), (0, react.createElement)("path", {
3413
+ key: "sound",
3414
+ d: "M15 8a6 6 0 0 1 0 8m3-11a10 10 0 0 1 0 14"
3415
+ })]);
3416
+ }
3417
+ const groupCss = `
3418
+ [data-codingns-voice-settings-group]>summary::-webkit-details-marker{display:none}
3419
+ [data-codingns-voice-settings-group]>summary:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.06))}
3420
+ [data-codingns-voice-settings-group]>summary:focus-visible{outline:2px solid var(--dsw-alias-button-info-fill,#1677ff);outline-offset:2px}
3421
+ `;
3422
+ //#endregion
3423
+ //#region src/client/features/assistant-voice-initialization.ts
3424
+ /** 打开面板只检查缓存,用户点击启用后才安装;首次不展示技术参数或要求填写字段。 */
3425
+ function AssistantVoiceInitializationPanel({ services, enabled, active, inputSettings, outputSettings }) {
3426
+ const t = require_transport.useCodingNsTranslator(services.locale);
3427
+ const [snapshot, setSnapshot] = (0, react.useState)();
3428
+ const [pending, setPending] = (0, react.useState)(false);
3429
+ const [error, setError] = (0, react.useState)("");
3430
+ const [speaking, setSpeaking] = (0, react.useState)(false);
3431
+ const [writable, setWritable] = (0, react.useState)(() => services.settings.getSnapshot().writable);
3432
+ const mounted = (0, react.useRef)(false);
3433
+ const submitting = (0, react.useRef)(false);
3434
+ const reading = (0, react.useRef)(false);
3435
+ const revision = (0, react.useRef)(0);
3436
+ const abort = (0, react.useRef)();
3437
+ const player = (0, react.useRef)();
3438
+ if (player.current === void 0) player.current = new require_react_dom$1.MossVoiceOutput();
3439
+ const refresh = (0, react.useCallback)(async () => {
3440
+ if (reading.current) return;
3441
+ reading.current = true;
3442
+ const current = revision.current;
3443
+ try {
3444
+ const result = await services.rpc.call(require_transport.CODINGNS_RPC_CHANNEL, "assistant/voice/initialization", {}, abort.current?.signal);
3445
+ if (!result.ok) throw new Error(result.error.message);
3446
+ if (mounted.current && current === revision.current) {
3447
+ setSnapshot(result.value);
3448
+ setError("");
3449
+ }
3450
+ } catch (cause) {
3451
+ if (mounted.current && current === revision.current) setError(message$1(cause));
3452
+ } finally {
3453
+ reading.current = false;
3454
+ }
3455
+ }, [services.rpc]);
3456
+ (0, react.useEffect)(() => {
3457
+ mounted.current = true;
3458
+ abort.current = new AbortController();
3459
+ const dispose = services.settings.subscribe(() => {
3460
+ setWritable(services.settings.getSnapshot().writable);
3461
+ refresh();
3462
+ });
3463
+ return () => {
3464
+ mounted.current = false;
3465
+ revision.current++;
3466
+ abort.current?.abort();
3467
+ player.current?.dispose();
3468
+ dispose();
3469
+ };
3470
+ }, [services.settings, refresh]);
3471
+ (0, react.useEffect)(() => {
3472
+ if (!active && !pending && !snapshot?.busy) return void 0;
3473
+ refresh();
3474
+ const timer = setInterval(() => {
3475
+ refresh();
3476
+ }, pending || snapshot?.busy ? 1e3 : 5e3);
3477
+ return () => clearInterval(timer);
3478
+ }, [
3479
+ active,
3480
+ pending,
3481
+ snapshot?.busy,
3482
+ refresh
3483
+ ]);
3484
+ (0, react.useEffect)(() => {
3485
+ if (!active || !enabled) {
3486
+ player.current?.cancel();
3487
+ setSpeaking(false);
3488
+ }
3489
+ }, [active, enabled]);
3490
+ const initialize = async () => {
3491
+ if (submitting.current || !enabled || !writable || snapshot === void 0 || snapshot.busy) return;
3492
+ submitting.current = true;
3493
+ revision.current++;
3494
+ setPending(true);
3495
+ setError("");
3496
+ player.current?.cancel();
3497
+ setSpeaking(false);
3498
+ try {
3499
+ const result = await services.rpc.call(require_transport.CODINGNS_RPC_CHANNEL, "assistant/voice/initialize", {}, abort.current?.signal);
3500
+ if (!result.ok) throw new Error(result.error.message);
3501
+ revision.current++;
3502
+ if (mounted.current) setSnapshot(result.value);
3503
+ if (services.settings.reload !== void 0) await services.settings.reload();
3504
+ else await services.settings.load?.();
3505
+ } catch (cause) {
3506
+ if (mounted.current) setError(message$1(cause));
3507
+ } finally {
3508
+ submitting.current = false;
3509
+ if (mounted.current) setPending(false);
3510
+ }
3511
+ };
3512
+ const listen = async () => {
3513
+ if (speaking) {
3514
+ player.current?.cancel();
3515
+ setSpeaking(false);
3516
+ return;
3517
+ }
3518
+ if (!enabled || !writable || !snapshot?.ready) return;
3519
+ setSpeaking(true);
3520
+ setError("");
3521
+ try {
3522
+ await player.current.speak(t("tts.defaultText"), snapshot.tts.settings.selectedId, void 0, snapshot.tts.settings.parameters);
3523
+ } catch (cause) {
3524
+ if (mounted.current) setError(message$1(cause));
3525
+ } finally {
3526
+ if (mounted.current) setSpeaking(false);
3527
+ }
3528
+ };
3529
+ return (0, react.createElement)("div", { style: {
3530
+ display: "grid",
3531
+ gap: 18
3532
+ } }, (0, react.createElement)(AssistantVoiceInitializationView, {
3533
+ snapshot,
3534
+ pending,
3535
+ error,
3536
+ disabled: !enabled || !writable,
3537
+ speaking,
3538
+ draft: services.configurationDraft === true,
3539
+ t,
3540
+ onInitialize: () => {
3541
+ initialize();
3542
+ },
3543
+ onListen: () => {
3544
+ listen();
3545
+ },
3546
+ onRefresh: () => {
3547
+ refresh();
3548
+ }
3549
+ }), (0, react.createElement)(AssistantVoiceSettingsGroup, {
3550
+ kind: "input",
3551
+ hint: snapshot?.modelLabel ?? t("voice.settings.inputDescription"),
3552
+ active,
3553
+ disabled: !enabled || !writable || pending || snapshot?.busy === true,
3554
+ t,
3555
+ renderContent: inputSettings
3556
+ }), (0, react.createElement)(AssistantVoiceSettingsGroup, {
3557
+ kind: "output",
3558
+ hint: snapshot?.tts.settings.backend === "browser" ? t("tts.browser") : snapshot?.tts.voices.find((voice) => voice.id === snapshot.tts.settings.selectedId)?.name ?? t("voice.settings.outputDescription"),
3559
+ active,
3560
+ disabled: !enabled || !writable || pending || snapshot?.busy === true,
3561
+ t,
3562
+ renderContent: outputSettings
3563
+ }));
3564
+ }
3565
+ /** 纯展示层覆盖未配置、准备中、失败与完成四态,重试仍只有一个入口。 */
3566
+ function AssistantVoiceInitializationView({ snapshot, pending, error, disabled, speaking, draft = false, t, onInitialize, onListen, onRefresh }) {
3567
+ const busy = pending || snapshot?.busy === true;
3568
+ const ready = snapshot?.ready === true;
3569
+ const failure = error || snapshot?.error;
3570
+ const voice = snapshot?.tts.voices.find((voice) => voice.id === snapshot.tts.settings.selectedId);
3571
+ const label = busy ? t(snapshot?.phase === "speech" || snapshot?.tts.status.busy ? "tts.wizardSpeech" : "tts.wizardRecognition") : ready ? t(draft ? "tts.wizardDraftReady" : "tts.wizardReady") : snapshot === void 0 ? t("tts.wizardChecking") : t("tts.wizardDescription");
3572
+ const progress = snapshot?.progress;
3573
+ const value = busy && progress?.totalBytes != null && progress.totalBytes > 0 ? Math.min(progress.downloadedBytes, progress.totalBytes) : void 0;
3574
+ return (0, react.createElement)("section", {
3575
+ "data-codingns-voice-initialization": ready ? "ready" : busy ? "preparing" : failure ? "failed" : "initial",
3576
+ "aria-label": t("tts.wizardTitle"),
3577
+ style: {
3578
+ display: "grid",
3579
+ gap: 12,
3580
+ padding: 18,
3581
+ borderRadius: 12,
3582
+ border: `1px solid ${require_theme.dshThemeColor.border}`,
3583
+ background: require_theme.dshThemeColor.surfaceSubtle
3584
+ }
3585
+ }, (0, react.createElement)("strong", null, t(ready ? "tts.wizardReady" : "tts.wizardTitle")), (0, react.createElement)("p", {
3586
+ role: "status",
3587
+ "aria-live": "polite",
3588
+ style: {
3589
+ ...require_theme.dshSettingsHelpStyle,
3590
+ margin: 0
3591
+ }
3592
+ }, label), ready ? null : (0, react.createElement)("div", { style: {
3593
+ display: "grid",
3594
+ gap: 5,
3595
+ fontSize: 13,
3596
+ overflowWrap: "anywhere"
3597
+ } }, (0, react.createElement)("span", null, t("tts.wizardModel", { model: snapshot?.modelId === "custom" ? t("voice.models.custom") : snapshot?.modelLabel ?? t("tts.wizardDefault") })), (0, react.createElement)("span", null, t("tts.wizardVoice", { voice: voice?.name ?? "Junhao" }))), busy ? (0, react.createElement)("div", { style: {
3598
+ display: "grid",
3599
+ gap: 6
3600
+ } }, (0, react.createElement)("progress", {
3601
+ max: progress?.totalBytes ?? 1,
3602
+ ...value === void 0 ? {} : { value },
3603
+ "aria-label": label,
3604
+ style: { width: "100%" }
3605
+ }), snapshot?.phase !== "speech" || progress === null || progress === void 0 ? null : (0, react.createElement)("small", { style: require_theme.dshSettingsHelpStyle }, progress.label)) : null, ready ? null : (0, react.createElement)("p", { style: {
3606
+ ...require_theme.dshSettingsHelpStyle,
3607
+ margin: 0
3608
+ } }, t("tts.wizardHint")), (0, react.createElement)("div", { style: {
3609
+ display: "flex",
3610
+ flexWrap: "wrap",
3611
+ gap: 8
3612
+ } }, (0, react.createElement)("button", {
3613
+ type: "button",
3614
+ disabled: disabled || busy || snapshot === void 0,
3615
+ onClick: ready ? onListen : onInitialize,
3616
+ style: require_theme.dshSettingsPrimaryButtonStyle
3617
+ }, t(ready ? speaking ? "tts.stop" : "tts.wizardListen" : busy ? "tts.wizardPreparing" : failure ? "tts.wizardRetry" : draft ? "tts.wizardPrepareResources" : "tts.wizardEnable")), snapshot !== void 0 && !failure ? null : (0, react.createElement)("button", {
3618
+ type: "button",
3619
+ disabled: busy,
3620
+ onClick: onRefresh,
3621
+ style: require_theme.dshSettingsButtonStyle
3622
+ }, t("tts.refresh"))), failure ? (0, react.createElement)("div", {
3623
+ role: "alert",
3624
+ style: {
3625
+ color: require_theme.dshThemeColor.error,
3626
+ fontSize: 13,
3627
+ overflowWrap: "anywhere"
3628
+ }
3629
+ }, failure) : null);
3630
+ }
3631
+ function message$1(cause) {
3632
+ return cause instanceof Error ? cause.message : String(cause);
3633
+ }
3634
+ //#endregion
3635
+ //#region src/client/voice-model-setup-progress.ts
3636
+ /** 复用页面 RPC 查询下载状态;每次查询返回后再安排下一次,避免请求积压。 */
3637
+ function watchVoiceModelSetupProgress(rpc, request, onProgress) {
3638
+ let active = true;
3639
+ let timer;
3640
+ const controller = new AbortController();
3641
+ const poll = async () => {
3642
+ try {
3643
+ const result = await rpc.call(require_transport.CODINGNS_RPC_CHANNEL, "assistant/voice/setup-progress", request, controller.signal);
3644
+ const progress = result.ok ? result.value : null;
3645
+ if (active && progress != null && progress.modelId === request.modelId) onProgress(progress);
3646
+ } catch {}
3647
+ if (active) timer = setTimeout(() => void poll(), 300);
3648
+ };
3649
+ poll();
3650
+ return () => {
3651
+ active = false;
3652
+ clearTimeout(timer);
3653
+ controller.abort();
3654
+ };
3655
+ }
3656
+ //#endregion
3657
+ //#region src/client/features/voice-initialization-dialog.ts
3658
+ /** 识别模型独立展示,不依赖 MOSS 初始化或高级声音设置的展开状态。 */
3659
+ function VoiceModelManagerPanel({ services, value, enabled, active }) {
3660
+ return (0, react.createElement)(VoiceModelManager, {
3661
+ services,
3662
+ value,
3663
+ enabled,
3664
+ active
3665
+ });
3666
+ }
3667
+ /** 选中的卡片与 Host 当前配置分开保存,操作完成后重新读取文件事实。 */
3668
+ function VoiceModelManager({ services, value, enabled = true, active = true, onClose }) {
3669
+ const t = require_transport.useCodingNsTranslator(services.locale);
3670
+ const [modelId, setModelId] = (0, react.useState)(() => require_assistant_workbench.findAssistantVoiceModel(value.modelId ?? "")?.id ?? "sherpa-onnx-streaming-zh-en");
3671
+ const [snapshot, setSnapshot] = (0, react.useState)();
3672
+ const [refreshing, setRefreshing] = (0, react.useState)(true);
3673
+ const [busy, setBusy] = (0, react.useState)();
3674
+ const [progress, setProgress] = (0, react.useState)();
3675
+ const [error, setError] = (0, react.useState)();
3676
+ const [loadError, setLoadError] = (0, react.useState)();
3677
+ const [success, setSuccess] = (0, react.useState)();
3678
+ const mounted = (0, react.useRef)(true);
3679
+ const readAbort = (0, react.useRef)();
3680
+ const stopProgress = (0, react.useRef)();
3681
+ const pending = (0, react.useRef)(false);
3682
+ const [settings, setSettings] = (0, react.useState)(() => services.settings.getSnapshot());
3683
+ const refresh = (0, react.useCallback)(async () => {
3684
+ readAbort.current?.abort();
3685
+ const controller = new AbortController();
3686
+ readAbort.current = controller;
3687
+ setRefreshing(true);
3688
+ try {
3689
+ const result = await services.rpc.call(require_transport.CODINGNS_RPC_CHANNEL, "assistant/voice/models", {}, controller.signal);
3690
+ if (!mounted.current || controller.signal.aborted) return;
3691
+ if (!result.ok) throw new Error(result.error.message);
3692
+ setSnapshot(result.value);
3693
+ setLoadError(void 0);
3694
+ } catch (cause) {
3695
+ if (mounted.current && !controller.signal.aborted) setLoadError(errorMessage(cause));
3696
+ } finally {
3697
+ if (mounted.current && !controller.signal.aborted) setRefreshing(false);
3698
+ }
3699
+ }, [services.rpc]);
3700
+ (0, react.useEffect)(() => {
3701
+ mounted.current = true;
3702
+ return () => {
3703
+ mounted.current = false;
3704
+ readAbort.current?.abort();
3705
+ stopProgress.current?.();
3706
+ };
3707
+ }, [services.rpc]);
3708
+ (0, react.useEffect)(() => {
3709
+ if (active) refresh();
3710
+ const onFocus = () => {
3711
+ if (active) refresh();
3712
+ };
3713
+ window.addEventListener("focus", onFocus);
3714
+ const unsubscribe = services.settings.subscribe(() => {
3715
+ setSettings(services.settings.getSnapshot());
3716
+ if (active || pending.current) refresh();
3717
+ });
3718
+ return () => {
3719
+ window.removeEventListener("focus", onFocus);
3720
+ unsubscribe();
3721
+ };
3722
+ }, [
3723
+ active,
3724
+ refresh,
3725
+ services.settings
3726
+ ]);
3727
+ (0, react.useEffect)(() => {
3728
+ const current = require_assistant_workbench.findAssistantVoiceModel((services.configurationDraft ? value.modelId : snapshot?.currentModelId) ?? "");
3729
+ if (current !== void 0) setModelId(current.id);
3730
+ }, [
3731
+ snapshot?.currentModelId,
3732
+ services.configurationDraft,
3733
+ value.modelId
3734
+ ]);
3735
+ (0, react.useEffect)(() => {
3736
+ if (!active || snapshot?.operation == null || busy !== void 0) return void 0;
3737
+ const timer = setTimeout(() => void refresh(), 1e3);
3738
+ return () => clearTimeout(timer);
3739
+ }, [
3740
+ active,
3741
+ snapshot,
3742
+ busy,
3743
+ refresh
3744
+ ]);
3745
+ (0, react.useEffect)(() => {
3746
+ if (onClose === void 0) return void 0;
3747
+ const onEscape = (event) => {
3748
+ if (event.key === "Escape" && !pending.current) onClose();
3749
+ };
3750
+ document.addEventListener("keydown", onEscape);
3751
+ return () => document.removeEventListener("keydown", onEscape);
3752
+ }, [onClose]);
3753
+ const run = async (kind) => {
3754
+ if (!active || !enabled || pending.current || snapshot === void 0 || snapshot.operation !== null) return;
3755
+ if (kind !== "verify" && (snapshot.runtimeRunning || !settings.writable || settings.status === "loading")) return;
3756
+ pending.current = true;
3757
+ setBusy(kind);
3758
+ setError(void 0);
3759
+ setSuccess(void 0);
3760
+ setProgress(void 0);
3761
+ try {
3762
+ const request = {
3763
+ modelId,
3764
+ requestId: `voice-model-${Date.now()}-${Math.random().toString(36).slice(2)}`,
3765
+ repair: kind === "repair"
3766
+ };
3767
+ if (kind !== "verify") stopProgress.current = watchVoiceModelSetupProgress(services.rpc, request, (next) => {
3768
+ if (mounted.current) setProgress(next);
3769
+ });
3770
+ const result = await services.rpc.call(require_transport.CODINGNS_RPC_CHANNEL, kind === "verify" ? "assistant/voice/model/verify" : "assistant/voice/setup", request);
3771
+ if (!mounted.current) return;
3772
+ if (!result.ok) throw new Error(result.error.message);
3773
+ if (kind !== "verify") {
3774
+ if (services.settings.reload !== void 0) await services.settings.reload();
3775
+ else await services.settings.load?.();
3776
+ }
3777
+ if (!mounted.current) return;
3778
+ setSuccess(t(kind === "verify" ? "voice.models.verified" : services.configurationDraft ? "voice.models.prepared" : "voice.models.applied"));
3779
+ } catch (cause) {
3780
+ if (mounted.current) setError(errorMessage(cause));
3781
+ } finally {
3782
+ stopProgress.current?.();
3783
+ stopProgress.current = void 0;
3784
+ pending.current = false;
3785
+ if (mounted.current) {
3786
+ setBusy(void 0);
3787
+ await refresh();
3788
+ }
3789
+ }
3790
+ };
3791
+ const choose = (id) => {
3792
+ setModelId(id);
3793
+ setError(void 0);
3794
+ setSuccess(void 0);
3795
+ setProgress(void 0);
3796
+ };
3797
+ const manager = (0, react.createElement)(VoiceModelManagerView, {
3798
+ snapshot,
3799
+ modelId,
3800
+ refreshing,
3801
+ busy,
3802
+ progress,
3803
+ error: error ?? loadError,
3804
+ success,
3805
+ enabled,
3806
+ writable: settings.writable && settings.status !== "loading",
3807
+ onSelect: choose,
3808
+ onRefresh: () => void refresh(),
3809
+ onRun: (kind) => void run(kind),
3810
+ ...onClose === void 0 ? {} : { onClose },
3811
+ t
3812
+ });
3813
+ if (onClose === void 0) return (0, react.createElement)("section", {
3814
+ "data-codingns-voice-model-manager": true,
3815
+ "aria-label": t("awb.asr"),
3816
+ "aria-busy": refreshing || busy !== void 0,
3817
+ style: {
3818
+ display: "grid",
3819
+ minWidth: 0
3820
+ }
3821
+ }, manager);
3822
+ return (0, react.createElement)("div", {
3823
+ role: "presentation",
3824
+ onPointerDown: () => {
3825
+ if (!pending.current) onClose();
3826
+ },
3827
+ style: {
3828
+ position: "fixed",
3829
+ inset: 0,
3830
+ zIndex: 1e4,
3831
+ display: "flex",
3832
+ alignItems: "center",
3833
+ justifyContent: "center",
3834
+ padding: 16,
3835
+ background: require_theme.dshThemeColor.overlay,
3836
+ boxSizing: "border-box"
3837
+ }
3838
+ }, (0, react.createElement)("div", {
3839
+ role: "dialog",
3840
+ "aria-modal": true,
3841
+ "aria-label": t("voice.setup.title"),
3842
+ "aria-busy": busy !== void 0,
3843
+ onPointerDown: (event) => event.stopPropagation(),
3844
+ style: {
3845
+ display: "flex",
3846
+ flexDirection: "column",
3847
+ width: "min(680px, 100%)",
3848
+ maxHeight: "min(780px, 100%)",
3849
+ color: require_theme.dshThemeColor.labelPrimary,
3850
+ background: require_theme.dshThemeColor.menuBackground,
3851
+ border: `1px solid ${require_theme.dshThemeColor.border}`,
3852
+ borderRadius: 14,
3853
+ boxShadow: require_theme.dshThemeColor.prominentShadow,
3854
+ overflow: "hidden",
3855
+ boxSizing: "border-box"
3856
+ }
3857
+ }, manager));
3858
+ }
3859
+ /** 展示层保留下载、配置和验证三个维度,避免把选择动作显示成启用成功。 */
3860
+ function VoiceModelManagerView({ snapshot, modelId, refreshing, busy, progress, error, success, writable, enabled = true, onSelect, onRefresh, onRun, onClose, t }) {
3861
+ const current = require_assistant_workbench.findAssistantVoiceModel(snapshot?.currentModelId ?? "");
3862
+ const selected = snapshot?.models.find((model) => model.modelId === modelId);
3863
+ const locked = !enabled || busy !== void 0 || snapshot?.operation != null;
3864
+ const canApply = snapshot !== void 0 && !locked && writable && !snapshot.runtimeRunning;
3865
+ const alreadyUsed = selected?.current === true && selected.validation.state === "passed" && selected.state === "downloaded";
3866
+ const action = selected?.state === "partial" ? t("voice.models.completeDownload") : selected?.state === "downloaded" ? alreadyUsed ? t("voice.models.inUse") : t("voice.models.use") : t("voice.models.downloadUse");
3867
+ return (0, react.createElement)("div", { style: { display: "contents" } }, (0, react.createElement)("div", { style: {
3868
+ padding: onClose === void 0 ? "0 0 12px" : "20px 22px 16px",
3869
+ display: "grid",
3870
+ gap: 6
3871
+ } }, (0, react.createElement)("div", { style: {
3872
+ ...rowStyle,
3873
+ flexWrap: "wrap"
3874
+ } }, (0, react.createElement)("strong", { style: {
3875
+ fontSize: onClose === void 0 ? 14 : 19,
3876
+ lineHeight: 1.4
3877
+ } }, t(onClose === void 0 ? "awb.asr" : "voice.setup.title")), button(t(refreshing ? "voice.models.refreshing" : "voice.models.refresh"), refreshing || busy !== void 0, onRefresh)), (0, react.createElement)("span", { style: helpStyle }, t("voice.models.description")), onClose === void 0 ? (0, react.createElement)("span", { style: helpStyle }, t("voice.models.recognitionHint")) : null), (0, react.createElement)("div", { style: {
3878
+ ...onClose === void 0 ? {} : { overflowY: "auto" },
3879
+ minHeight: 0,
3880
+ padding: onClose === void 0 ? "0 0 16px" : "0 22px 18px",
3881
+ display: "grid",
3882
+ gap: 12
3883
+ } }, (0, react.createElement)("div", { style: {
3884
+ ...noticeStyle,
3885
+ display: "grid",
3886
+ gap: 5
3887
+ } }, (0, react.createElement)("span", { style: helpStyle }, t("voice.models.current")), (0, react.createElement)("strong", { style: {
3888
+ fontSize: 14,
3889
+ overflowWrap: "anywhere"
3890
+ } }, snapshot === void 0 ? t("voice.models.unknown") : current?.label ?? (snapshot.currentModelId ? t("voice.models.custom") : t("voice.models.none"))), snapshot === void 0 ? null : (0, react.createElement)("span", { style: helpStyle }, t(snapshot.runtimeRunning ? "voice.models.running" : snapshot.runtimeReady ? "voice.models.ready" : "voice.models.stopped"))), snapshot === void 0 && error === void 0 ? (0, react.createElement)("div", {
3891
+ role: "status",
3892
+ style: helpStyle
3893
+ }, t("voice.models.loading")) : null, (0, react.createElement)("div", {
3894
+ role: "radiogroup",
3895
+ "aria-label": t("voice.setup.model"),
3896
+ style: {
3897
+ display: "grid",
3898
+ gap: 10
3899
+ }
3900
+ }, ...require_assistant_workbench.ASSISTANT_VOICE_MODEL_CATALOG.map((model) => (0, react.createElement)(VoiceModelCard, {
3901
+ key: model.id,
3902
+ model,
3903
+ status: snapshot?.models.find((status) => status.modelId === model.id),
3904
+ selected: model.id === modelId,
3905
+ disabled: locked || snapshot === void 0,
3906
+ onSelect,
3907
+ t
3908
+ }))), busy === void 0 ? null : (0, react.createElement)("div", { style: noticeStyle }, (0, react.createElement)(VoiceModelSetupProgress, {
3909
+ progress: busy === "verify" ? {
3910
+ modelId,
3911
+ phase: "verifying",
3912
+ fileName: null,
3913
+ fileIndex: 0,
3914
+ fileCount: 0,
3915
+ downloadedBytes: 0,
3916
+ totalBytes: null
3917
+ } : progress,
3918
+ t
3919
+ })), snapshot?.operation == null || busy !== void 0 ? null : (0, react.createElement)("div", {
3920
+ role: "status",
3921
+ style: noticeStyle
3922
+ }, t("voice.models.otherOperation")), snapshot?.runtimeRunning ? (0, react.createElement)("div", { style: helpStyle }, t("voice.models.stopBeforeSwitch")) : null, success === void 0 ? null : (0, react.createElement)("div", {
3923
+ role: "status",
3924
+ style: {
3925
+ ...noticeStyle,
3926
+ color: require_theme.dshThemeColor.success
3927
+ }
3928
+ }, success), error === void 0 ? null : (0, react.createElement)("div", {
3929
+ role: "alert",
3930
+ style: {
3931
+ ...noticeStyle,
3932
+ color: require_theme.dshThemeColor.error,
3933
+ overflowWrap: "anywhere",
3934
+ whiteSpace: "pre-wrap"
3935
+ }
3936
+ }, error), (0, react.createElement)("span", { style: helpStyle }, t("voice.models.localHint"))), (0, react.createElement)("div", { style: {
3937
+ display: "flex",
3938
+ justifyContent: "flex-end",
3939
+ flexWrap: "wrap",
3940
+ gap: 8,
3941
+ padding: onClose === void 0 ? "14px 0 0" : "14px 22px",
3942
+ borderTop: `1px solid ${require_theme.dshThemeColor.border}`
3943
+ } }, onClose === void 0 ? null : button(t("voice.models.close"), busy !== void 0, onClose), selected?.state === "missing" || selected === void 0 ? null : button(t("voice.models.redownload"), !canApply, () => onRun("repair")), button(t("voice.models.verify"), locked || selected?.state !== "downloaded", () => onRun("verify")), button(action, !canApply || alreadyUsed || selected === void 0, () => onRun("setup"), true)));
3944
+ }
3945
+ /** 文件详情按需展开,模型下载状态与最近验证结果始终可见。 */
3946
+ function VoiceModelCard({ model, status, selected, disabled, onSelect, t }) {
3947
+ const state = status === void 0 ? t("voice.models.unknown") : t(status.state === "downloaded" ? "voice.models.downloaded" : status.state === "partial" ? "voice.models.partial" : "voice.models.missing");
3948
+ const validation = status?.validation.state === "passed" ? t("voice.models.passed") : status?.validation.state === "failed" ? t("voice.models.failed") : t("voice.models.unchecked");
3949
+ return (0, react.createElement)("div", { style: {
3950
+ border: `1px solid ${selected ? require_theme.dshThemeColor.accent : require_theme.dshThemeColor.border}`,
3951
+ borderRadius: 9,
3952
+ padding: "12px 14px",
3953
+ background: selected ? require_theme.dshThemeColor.surfaceSubtle : require_theme.dshThemeColor.menuBackground,
3954
+ display: "grid",
3955
+ gap: 8
3956
+ } }, (0, react.createElement)("label", { style: {
3957
+ ...require_assistant_workbench.assistantSettingCheckboxStyle,
3958
+ cursor: disabled ? "default" : "pointer"
3959
+ } }, (0, react.createElement)("input", {
3960
+ type: "radio",
3961
+ name: "voice-model",
3962
+ value: model.id,
3963
+ checked: selected,
3964
+ disabled,
3965
+ onChange: () => onSelect(model.id),
3966
+ style: {
3967
+ margin: "3px 0 0",
3968
+ accentColor: require_theme.dshThemeColor.accent,
3969
+ flexShrink: 0
3970
+ }
3971
+ }), (0, react.createElement)("span", { style: {
3972
+ display: "grid",
3973
+ gap: 5,
3974
+ minWidth: 0,
3975
+ flex: 1
3976
+ } }, (0, react.createElement)("span", { style: {
3977
+ ...require_assistant_workbench.assistantSettingTextStyle,
3978
+ overflowWrap: "anywhere"
3979
+ } }, model.label), (0, react.createElement)("span", { style: helpStyle }, model.description), (0, react.createElement)("span", { style: {
3980
+ display: "flex",
3981
+ flexWrap: "wrap",
3982
+ gap: 6
3983
+ } }, badge(state, status?.state === "downloaded" ? require_theme.dshThemeColor.success : require_theme.dshThemeColor.labelSecondary), status?.current ? badge(t("voice.models.inUse"), require_theme.dshThemeColor.accent) : null, status?.state === "downloaded" ? badge(validation, status.validation.state === "passed" ? require_theme.dshThemeColor.success : status.validation.state === "failed" ? require_theme.dshThemeColor.error : require_theme.dshThemeColor.labelSecondary) : null))), status === void 0 ? null : (0, react.createElement)("div", { style: {
3984
+ paddingLeft: 23,
3985
+ display: "grid",
3986
+ gap: 6
3987
+ } }, (0, react.createElement)("span", { style: helpStyle }, t("voice.models.fileCount", {
3988
+ count: status.files.filter((file) => file.present).length,
3989
+ total: status.files.length,
3990
+ size: formatDownloadSize(status.totalBytes)
3991
+ })), status.validation.checkedAt === null ? null : (0, react.createElement)("span", { style: helpStyle }, t("voice.models.checkedAt", { time: new Date(status.validation.checkedAt).toLocaleString() })), status.validation.error === null ? null : (0, react.createElement)("span", { style: {
3992
+ ...helpStyle,
3993
+ color: require_theme.dshThemeColor.error,
3994
+ overflowWrap: "anywhere"
3995
+ } }, status.validation.error), (0, react.createElement)("details", null, (0, react.createElement)("summary", { style: {
3996
+ ...helpStyle,
3997
+ cursor: "pointer"
3998
+ } }, t("voice.models.fileDetails")), (0, react.createElement)("div", { style: {
3999
+ display: "grid",
4000
+ gap: 9,
4001
+ marginTop: 9
4002
+ } }, ...status.files.map((file) => (0, react.createElement)("div", {
4003
+ key: file.name,
4004
+ style: {
4005
+ display: "grid",
4006
+ gap: 2
4007
+ }
4008
+ }, (0, react.createElement)("span", { style: {
4009
+ ...rowStyle,
4010
+ fontSize: 12
4011
+ } }, (0, react.createElement)("span", { style: { overflowWrap: "anywhere" } }, file.name), (0, react.createElement)("span", { style: {
4012
+ flexShrink: 0,
4013
+ color: file.present ? require_theme.dshThemeColor.labelSecondary : require_theme.dshThemeColor.error
4014
+ } }, file.present ? formatDownloadSize(file.bytes) : file.partialBytes > 0 ? t("voice.models.partialSize", { size: formatDownloadSize(file.partialBytes) }) : t("voice.models.fileMissing"))), (0, react.createElement)("span", { style: {
4015
+ ...helpStyle,
4016
+ fontSize: 11,
4017
+ overflowWrap: "anywhere",
4018
+ fontFamily: require_theme.dshThemeColor.codeFont
4019
+ } }, file.path)))))));
4020
+ }
4021
+ /** 已下载的字节进度与模型验证、配置阶段分别显示。 */
4022
+ function VoiceModelSetupProgress({ progress, t }) {
4023
+ const downloading = progress?.phase === "downloading";
4024
+ const percent = progress?.phase === "initializing" || progress?.phase === "completed" ? 100 : downloading && progress.totalBytes !== null && progress.totalBytes > 0 ? Math.min(100, Math.floor(progress.downloadedBytes / progress.totalBytes * 100)) : void 0;
4025
+ const label = downloading ? t("voice.setup.downloadFile", {
4026
+ index: progress.fileIndex,
4027
+ count: progress.fileCount
4028
+ }) : progress?.phase === "verifying" ? t("voice.models.verifying") : progress?.phase === "initializing" ? t("voice.setup.initializing") : progress?.phase === "completed" ? t("voice.setup.completed") : t("voice.setup.checking");
4029
+ const bytes = downloading ? progress.totalBytes === null ? t("voice.setup.downloadedSize", { size: formatDownloadSize(progress.downloadedBytes) }) : `${formatDownloadSize(progress.downloadedBytes)} / ${formatDownloadSize(progress.totalBytes)}` : void 0;
4030
+ return (0, react.createElement)("div", {
4031
+ role: "status",
4032
+ "aria-live": "polite",
4033
+ style: {
4034
+ display: "grid",
4035
+ gap: 7,
4036
+ color: require_theme.dshThemeColor.labelSecondary,
4037
+ fontSize: 13,
4038
+ lineHeight: 1.5
4039
+ }
4040
+ }, (0, react.createElement)("div", { style: rowStyle }, (0, react.createElement)("span", null, label), percent === void 0 ? null : (0, react.createElement)("span", { style: {
4041
+ flexShrink: 0,
4042
+ fontVariantNumeric: "tabular-nums"
4043
+ } }, downloading ? t("voice.setup.filePercent", { percent }) : "100%")), (0, react.createElement)("progress", {
4044
+ max: 100,
4045
+ ...percent === void 0 ? {} : { value: percent },
4046
+ "aria-label": downloading ? t("voice.setup.fileProgress") : label,
4047
+ style: {
4048
+ display: "block",
4049
+ width: "100%",
4050
+ height: 10,
4051
+ accentColor: require_theme.dshThemeColor.accent
4052
+ }
4053
+ }), downloading ? (0, react.createElement)("span", { style: { overflowWrap: "anywhere" } }, progress.fileName) : null, bytes === void 0 ? null : (0, react.createElement)("span", { style: { fontVariantNumeric: "tabular-nums" } }, bytes));
4054
+ }
4055
+ function formatDownloadSize(bytes) {
4056
+ if (bytes < 1024) return `${bytes} B`;
4057
+ if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
4058
+ return `${(bytes / 1048576).toFixed(1)} MB`;
4059
+ }
4060
+ function errorMessage(cause) {
4061
+ return cause instanceof Error ? cause.message : String(cause);
4062
+ }
4063
+ function button(label, disabled, onClick, primary = false) {
4064
+ return (0, react.createElement)("button", {
4065
+ type: "button",
4066
+ disabled,
4067
+ onClick,
4068
+ style: {
4069
+ ...primary ? require_theme.dshSettingsPrimaryButtonStyle : require_theme.dshSettingsButtonStyle,
4070
+ whiteSpace: "nowrap",
4071
+ flexShrink: 0,
4072
+ ...disabled ? {
4073
+ opacity: .5,
4074
+ cursor: "default"
4075
+ } : {}
4076
+ }
4077
+ }, label);
4078
+ }
4079
+ function badge(label, color) {
4080
+ return (0, react.createElement)("span", { style: {
4081
+ padding: "2px 7px",
4082
+ border: `1px solid ${require_theme.dshThemeColor.border}`,
4083
+ borderRadius: 5,
4084
+ fontSize: 11,
4085
+ lineHeight: 1.4,
4086
+ color
4087
+ } }, label);
4088
+ }
4089
+ const rowStyle = {
4090
+ display: "flex",
4091
+ justifyContent: "space-between",
4092
+ alignItems: "center",
4093
+ gap: 12
4094
+ };
4095
+ const helpStyle = {
4096
+ color: require_theme.dshThemeColor.labelSecondary,
4097
+ fontSize: 12,
4098
+ lineHeight: 1.55
4099
+ };
4100
+ const noticeStyle = {
4101
+ padding: "11px 13px",
4102
+ borderRadius: 8,
4103
+ background: require_theme.dshThemeColor.surfaceSubtle,
4104
+ fontSize: 13,
4105
+ lineHeight: 1.55
4106
+ };
4107
+ //#endregion
4108
+ //#region src/client/features/assistant-configuration-view.ts
4109
+ /** 创建与配置共用形象选择:选中 Live2D 时先确认引擎许可并自动安装,成功后才改草稿。 */
4110
+ function AssistantAvatarSelectionField({ services, appearance, avatarId, avatars, includeLegacy, disabled, t, onChange }) {
4111
+ const engine = useAssistantAvatarEngine({
4112
+ services,
4113
+ manager: require_manager.getAssistantAvatarManager(services),
4114
+ appearance,
4115
+ disabled
4116
+ });
4117
+ const choices = assistantAvatarChoices(appearance, [], t, includeLegacy);
4118
+ const avatar = avatars.find((model) => model.id === avatarId);
4119
+ const list = choices.some((choice) => choice.id === avatarId) ? choices : [{
4120
+ id: avatarId,
4121
+ label: t("awb.unavailableSelection", { id: avatarId }),
4122
+ thirdParty: false,
4123
+ disabled: true
4124
+ }, ...choices];
4125
+ const choose = (choice) => {
4126
+ if (disabled || !avatars.some((model) => model.id === choice.id)) return;
4127
+ if (avatars.find((item) => item.id === choice.id)?.renderer === "live2d") {
4128
+ engine.ensure(() => onChange({ avatarId: choice.id }));
4129
+ return;
4130
+ }
4131
+ onChange({ avatarId: choice.id });
4132
+ };
4133
+ return (0, react.createElement)("div", { style: {
4134
+ display: "grid",
4135
+ gap: 8
4136
+ } }, (0, react.createElement)(AssistantAvatarPicker, {
4137
+ label: t("awb.avatar"),
4138
+ value: avatarId,
4139
+ disabled: disabled || engine.installing,
4140
+ t,
4141
+ choices: list,
4142
+ onChoose: choose
4143
+ }), (0, react.createElement)(AssistantAvatarEngineProgress, {
4144
+ controller: engine,
4145
+ t
4146
+ }), (0, react.createElement)("p", { style: help }, t("avatar.externalHint")), avatar?.renderer !== "live2d" ? null : (0, react.createElement)("p", { style: help }, t("avatar.live2dDependencyHint")), (0, react.createElement)(AssistantAvatarEngineDialog, {
4147
+ controller: engine,
4148
+ t
4149
+ }));
4150
+ }
4151
+ function AssistantConfigurationFields({ draft, catalog, appearance, initializing = false, includeAvatar = true, services, t, disabled, onChange }) {
4152
+ const models = catalog?.models ?? [];
4153
+ const avatars = require_manager.listAssistantAvatars(appearance, !initializing);
4154
+ const avatar = avatars.find((model) => model.id === draft.avatarId);
4155
+ const avatarChoices = assistantAvatarChoices(appearance, [], t, !initializing);
4156
+ const input = (label, node) => (0, react.createElement)("label", { style: field }, (0, react.createElement)("span", null, label), node);
4157
+ const select = (label, value, change, options) => input(label, (0, react.createElement)("select", {
4158
+ value,
4159
+ disabled,
4160
+ style: identityFieldStyle,
4161
+ onChange: (event) => change(event.currentTarget.value)
4162
+ }, ...options));
4163
+ const missing = (id) => (0, react.createElement)("option", {
4164
+ key: id,
4165
+ value: id
4166
+ }, t("awb.unavailableSelection", { id }));
4167
+ return (0, react.createElement)("div", {
4168
+ "data-codingns-assistant-identity": true,
4169
+ style: {
4170
+ display: "grid",
4171
+ gap: 11
4172
+ }
4173
+ }, (0, react.createElement)("div", {
4174
+ "data-codingns-assistant-identity-row": true,
4175
+ style: {
4176
+ display: "grid",
4177
+ alignItems: "start",
4178
+ gridTemplateColumns: services === void 0 || avatar === void 0 ? "minmax(0, 1fr)" : "minmax(0, 1fr) minmax(0, 176px)",
4179
+ columnGap: 16,
4180
+ rowGap: 8,
4181
+ minWidth: 0
4182
+ }
4183
+ }, input(t("awb.name"), (0, react.createElement)("input", {
4184
+ value: draft.name,
4185
+ maxLength: 80,
4186
+ disabled,
4187
+ placeholder: t("awb.namePlaceholder"),
4188
+ style: identityFieldStyle,
4189
+ onChange: (event) => onChange({ name: event.currentTarget.value })
4190
+ })), services === void 0 || avatar === void 0 ? null : (0, react.createElement)(AssistantAvatarPortraitEditor, {
4191
+ services,
4192
+ model: avatar,
4193
+ disabled,
4194
+ t
4195
+ })), select(t("awb.model"), draft.modelKey, (modelKey) => onChange({ modelKey }), [
4196
+ (0, react.createElement)("option", {
4197
+ key: "",
4198
+ value: ""
4199
+ }, t("awb.defaultModel")),
4200
+ ...draft.modelKey && !models.some((model) => JSON.stringify([model.provider, model.model]) === draft.modelKey) ? [missing(draft.modelKey)] : [],
4201
+ ...models.map((model) => (0, react.createElement)("option", {
4202
+ key: JSON.stringify([model.provider, model.model]),
4203
+ value: JSON.stringify([model.provider, model.model])
4204
+ }, `${model.label} · ${model.provider}`))
4205
+ ]), catalog?.models.length === 0 ? (0, react.createElement)("p", { style: help }, t("awb.noModel"), ...catalog.errors.map((error) => (0, react.createElement)("span", {
4206
+ key: error,
4207
+ style: { display: "block" }
4208
+ }, error))) : null, input(t("awb.personality"), (0, react.createElement)("textarea", {
4209
+ value: draft.personality,
4210
+ maxLength: require_manager.ASSISTANT_PERSONALITY_MAX_CHARS,
4211
+ rows: 4,
4212
+ disabled,
4213
+ placeholder: t("awb.personalityPlaceholder"),
4214
+ style: {
4215
+ ...identityFieldStyle,
4216
+ minHeight: 96,
4217
+ maxHeight: 200,
4218
+ resize: "vertical",
4219
+ lineHeight: 1.65
4220
+ },
4221
+ onChange: (event) => onChange({ personality: event.currentTarget.value })
4222
+ })), !includeAvatar ? null : services === void 0 ? (0, react.createElement)("div", { style: {
4223
+ display: "grid",
4224
+ gap: 8
4225
+ } }, (0, react.createElement)(AssistantAvatarPicker, {
4226
+ label: t("awb.avatar"),
4227
+ value: draft.avatarId,
4228
+ disabled,
4229
+ t,
4230
+ choices: avatarChoices.some((choice) => choice.id === draft.avatarId) ? avatarChoices : [{
4231
+ id: draft.avatarId,
4232
+ label: t("awb.unavailableSelection", { id: draft.avatarId }),
4233
+ thirdParty: false,
4234
+ disabled: true
4235
+ }, ...avatarChoices],
4236
+ onChoose: (choice) => {
4237
+ if (avatars.some((avatar) => avatar.id === choice.id)) onChange({ avatarId: choice.id });
4238
+ }
4239
+ }), (0, react.createElement)("p", { style: help }, t("avatar.externalHint")), avatar?.renderer !== "live2d" ? null : (0, react.createElement)("p", { style: help }, t("avatar.live2dDependencyHint"))) : (0, react.createElement)(AssistantAvatarSelectionField, {
4240
+ services,
4241
+ appearance,
4242
+ avatarId: draft.avatarId,
4243
+ avatars,
4244
+ includeLegacy: !initializing,
4245
+ disabled,
4246
+ t,
4247
+ onChange
4248
+ }));
4249
+ }
4250
+ /** 创建后的各页只装配自己的字段;页面切换不持有或重置父级草稿。 */
4251
+ function AssistantConfigurationPage({ tab, active = true, services, value, draft, catalog, appearance, tts, workspaces, t, disabled, onChange, onDebug, onError, onReset, ...previewProps }) {
4252
+ switch (tab) {
4253
+ case "basic": return (0, react.createElement)(AssistantConfigurationFields, {
4254
+ draft,
4255
+ catalog,
4256
+ appearance,
4257
+ includeAvatar: false,
4258
+ services,
4259
+ t,
4260
+ disabled,
4261
+ onChange
4262
+ });
4263
+ case "appearance": return (0, react.createElement)("div", { style: {
4264
+ display: "grid",
4265
+ gap: 11
4266
+ } }, (0, react.createElement)(AssistantAppearanceEditor, {
4267
+ services,
4268
+ enabled: !disabled,
4269
+ active,
4270
+ ...previewProps
4271
+ }));
4272
+ case "voice": return (0, react.createElement)("div", { style: {
4273
+ display: "grid",
4274
+ gap: 11
4275
+ } }, (0, react.createElement)(AssistantVoiceInitializationPanel, {
4276
+ services,
4277
+ enabled: !disabled,
4278
+ active,
4279
+ inputSettings: (groupActive) => (0, react.createElement)("div", { style: {
4280
+ display: "grid",
4281
+ gap: 11
4282
+ } }, (0, react.createElement)(AssistantAudioDevice, {
4283
+ services,
4284
+ direction: "input",
4285
+ active: groupActive,
4286
+ disabled,
4287
+ t,
4288
+ onError
4289
+ }), (0, react.createElement)(VoiceModelManagerPanel, {
4290
+ services,
4291
+ value: value.voice,
4292
+ enabled: !disabled,
4293
+ active: groupActive
4294
+ })),
4295
+ outputSettings: (groupActive) => (0, react.createElement)("div", { style: {
4296
+ display: "grid",
4297
+ gap: 11
4298
+ } }, (0, react.createElement)(AssistantAudioDevice, {
4299
+ services,
4300
+ direction: "output",
4301
+ active: groupActive,
4302
+ disabled,
4303
+ t,
4304
+ onError
4305
+ }), (0, react.createElement)(AssistantVoiceSettings, {
4306
+ services,
4307
+ enabled: !disabled,
4308
+ active: groupActive,
4309
+ embedded: true
4310
+ }))
4311
+ }));
4312
+ case "more": return (0, react.createElement)("div", { style: {
4313
+ display: "grid",
4314
+ gap: 11
4315
+ } }, (0, react.createElement)(AssistantWorkspaceFields, {
4316
+ draft,
4317
+ workspaces,
4318
+ t,
4319
+ disabled,
4320
+ onChange
4321
+ }), (0, react.createElement)(AssistantAdvancedSettings, {
4322
+ services,
4323
+ value,
4324
+ disabled,
4325
+ t,
4326
+ onDebug,
4327
+ onError
4328
+ }), (0, react.createElement)("div", { style: {
4329
+ paddingTop: 16,
4330
+ borderTop: `1px solid ${require_theme.dshThemeColor.border}`
4331
+ } }, (0, react.createElement)("button", {
4332
+ type: "button",
4333
+ "data-codingns-assistant-reset": true,
4334
+ disabled: disabled || onReset === void 0,
4335
+ style: {
4336
+ ...require_theme.dshSettingsButtonStyle,
4337
+ color: require_theme.dshThemeColor.error
4338
+ },
4339
+ onClick: onReset
4340
+ }, t("awb.reset"))));
4341
+ }
4342
+ }
4343
+ /** 兼容原能力表单入口;实际配置页分别装配项目字段与声音字段。 */
4344
+ function AssistantCapabilityFields({ draft, tts, workspaces, t, disabled, onChange }) {
4345
+ return (0, react.createElement)("div", { style: {
4346
+ display: "grid",
4347
+ gap: 18
4348
+ } }, AssistantWorkspaceFields({
4349
+ draft,
4350
+ workspaces,
4351
+ t,
4352
+ disabled,
4353
+ onChange
4354
+ }), AssistantVoiceFields({
4355
+ draft,
4356
+ tts,
4357
+ t,
4358
+ disabled,
4359
+ onChange
4360
+ }));
4361
+ }
4362
+ /** 项目范围放到更多设置,保留暂不可用项目以避免编辑时丢失原有选择。 */
4363
+ function AssistantWorkspaceFields({ draft, workspaces, t, disabled, onChange }) {
4364
+ const knownWorkspaces = new Set(workspaces.map((workspace) => workspace.workspaceId));
4365
+ return (0, react.createElement)("fieldset", {
4366
+ "data-codingns-assistant-projects": true,
4367
+ style: {
4368
+ border: 0,
4369
+ padding: 0,
4370
+ margin: 0,
4371
+ minWidth: 0
4372
+ },
4373
+ disabled
4374
+ }, (0, react.createElement)("legend", { style: {
4375
+ fontSize: 14,
4376
+ fontWeight: 600,
4377
+ padding: 0,
4378
+ marginBottom: 8
4379
+ } }, t("awb.workspaces")), (0, react.createElement)("p", { style: help }, t("awb.scopeHint")), (0, react.createElement)("div", { style: {
4380
+ display: "grid",
4381
+ gap: 8,
4382
+ maxHeight: 180,
4383
+ overflowY: "auto",
4384
+ marginTop: 8
4385
+ } }, ...[...workspaces, ...draft.managedWorkspaceIds.filter((id) => !knownWorkspaces.has(id)).map((workspaceId) => ({
4386
+ workspaceId,
4387
+ title: t("awb.offlineWorkspace", { id: workspaceId })
4388
+ }))].map((workspace) => (0, react.createElement)("label", {
4389
+ key: workspace.workspaceId,
4390
+ style: require_assistant_workbench.assistantSettingCheckboxStyle
4391
+ }, (0, react.createElement)("input", {
4392
+ type: "checkbox",
4393
+ checked: draft.managedWorkspaceIds.includes(workspace.workspaceId),
4394
+ onChange: (event) => onChange({ managedWorkspaceIds: event.currentTarget.checked ? [...draft.managedWorkspaceIds, workspace.workspaceId] : draft.managedWorkspaceIds.filter((id) => id !== workspace.workspaceId) })
4395
+ }), workspace.title))));
4396
+ }
4397
+ /** 没有安装 MOSS 时只有浏览器声音可选,不因标签拆分而放宽可用性校验。 */
4398
+ function AssistantVoiceFields({ draft, tts, t, disabled, onChange }) {
4399
+ const mossReady = tts?.status.ready === true;
4400
+ const voices = mossReady ? tts.voices : [];
4401
+ return (0, react.createElement)("div", {
4402
+ "data-codingns-assistant-voice-selection": true,
4403
+ style: {
4404
+ display: "grid",
4405
+ gap: 10
4406
+ }
4407
+ }, (0, react.createElement)("label", { style: field }, (0, react.createElement)("span", null, t("awb.voice")), (0, react.createElement)("select", {
4408
+ value: draft.ttsBackend === "browser" || !mossReady ? "browser" : draft.voiceId,
4409
+ disabled,
4410
+ style: identityFieldStyle,
4411
+ onChange: (event) => {
4412
+ const voice = event.currentTarget.value;
4413
+ if (voice === "browser") onChange({ ttsBackend: "browser" });
4414
+ else if (mossReady && voices.some((item) => item.id === voice)) onChange({
4415
+ ttsBackend: "moss-onnx",
4416
+ voiceId: voice
4417
+ });
4418
+ }
4419
+ }, (0, react.createElement)("option", { value: "browser" }, t("awb.browserVoice")), ...voices.map((voice) => (0, react.createElement)("option", {
4420
+ key: voice.id,
4421
+ value: voice.id
4422
+ }, voice.name)))), mossReady ? null : (0, react.createElement)("p", { style: help }, t("awb.mossSetupHint")));
4423
+ }
4424
+ function AssistantAdvancedSettings({ services, value, disabled, t, onDebug, onError }) {
4425
+ const defaults = require_assistant_prompts.readAssistantPrompts(value.prompts);
4426
+ const prompts = services.configurationDraft ? {
4427
+ ...defaults,
4428
+ ...value.prompts
4429
+ } : defaults;
4430
+ const savePrompt = async (kind, text) => {
4431
+ try {
4432
+ if (!await services.settings.mutate([{
4433
+ op: "set",
4434
+ path: [
4435
+ "assistant",
4436
+ "prompts",
4437
+ kind
4438
+ ],
4439
+ value: text
4440
+ }])) throw new Error(t("settings.moduleWriteRejected"));
4441
+ } catch (cause) {
4442
+ onError(message(cause));
4443
+ throw cause;
4444
+ }
4445
+ };
4446
+ return (0, react.createElement)("div", { style: {
4447
+ display: "grid",
4448
+ gap: 14,
4449
+ marginTop: 14
4450
+ } }, (0, react.createElement)("p", { style: help }, t("awb.moreHint")), ...["index", "chat"].map((kind) => (0, react.createElement)(require_assistant_prompt_editor.AssistantPromptEditor, {
4451
+ key: kind,
4452
+ kind,
4453
+ value: prompts[kind],
4454
+ disabled,
4455
+ onSave: savePrompt,
4456
+ ...services.configurationDraft ? { onChange: savePrompt } : {},
4457
+ t
4458
+ })), (0, react.createElement)("button", {
4459
+ type: "button",
4460
+ disabled,
4461
+ style: require_theme.dshSettingsButtonStyle,
4462
+ onClick: onDebug
4463
+ }, t("assistant.debug.open")));
4464
+ }
4465
+ /** 设备按输入、输出分组枚举,折叠时保留选择但停止设备查询。 */
4466
+ function AssistantAudioDevice({ services, direction, active, disabled, t, onError }) {
4467
+ const adapter = require_manager.getGlobalVoiceAdapter(services);
4468
+ const output = direction === "output";
4469
+ const [devices, setDevices] = (0, react.useState)([]);
4470
+ const [selectedId, setSelectedId] = (0, react.useState)((output ? adapter?.outputDeviceId : adapter?.inputDeviceId) ?? "");
4471
+ (0, react.useEffect)(() => {
4472
+ if (!active || output && !adapter?.outputDeviceSupported) return void 0;
4473
+ let stopped = false;
4474
+ const refresh = () => {
4475
+ (output ? adapter?.enumerateOutputDevices() : adapter?.enumerateInputDevices())?.then((devices) => {
4476
+ if (!stopped) setDevices(devices);
4477
+ }).catch(() => void 0);
4478
+ };
4479
+ refresh();
4480
+ globalThis.navigator?.mediaDevices?.addEventListener?.("devicechange", refresh);
4481
+ return () => {
4482
+ stopped = true;
4483
+ globalThis.navigator?.mediaDevices?.removeEventListener?.("devicechange", refresh);
4484
+ };
4485
+ }, [
4486
+ active,
4487
+ adapter,
4488
+ output
4489
+ ]);
4490
+ if (output && !adapter?.outputDeviceSupported) return null;
4491
+ return (0, react.createElement)("label", { style: field }, t(output ? "voice.outputDevice" : "voice.inputDevice"), (0, react.createElement)("select", {
4492
+ disabled,
4493
+ value: selectedId,
4494
+ style: require_theme.dshSettingsFieldStyle,
4495
+ onChange: (event) => {
4496
+ const id = event.currentTarget.value;
4497
+ (output ? adapter?.selectOutputDevice(id) : adapter?.selectInputDevice(id))?.then(() => setSelectedId(id)).catch((cause) => onError(message(cause)));
4498
+ }
4499
+ }, (0, react.createElement)("option", { value: "" }, t(output ? "voice.defaultOutput" : "voice.defaultMicrophone")), ...devices.map((device) => (0, react.createElement)("option", {
4500
+ key: device.deviceId,
4501
+ value: device.deviceId
4502
+ }, device.label || t(output ? "voice.unnamedOutput" : "voice.unnamedMicrophone")))));
4503
+ }
4504
+ function message(error) {
4505
+ return error instanceof Error ? error.message : String(error);
4506
+ }
4507
+ const help = {
4508
+ ...require_theme.dshSettingsHelpStyle,
4509
+ margin: 0
4510
+ };
4511
+ const field = require_assistant_workbench.assistantSettingFieldStyle;
4512
+ const identityFieldStyle = {
4513
+ ...require_theme.dshSettingsFieldStyle,
4514
+ borderRadius: 8
4515
+ };
4516
+ //#endregion
4517
+ exports.AssistantCapabilityFields = AssistantCapabilityFields;
4518
+ exports.AssistantConfigurationFields = AssistantConfigurationFields;
4519
+ exports.AssistantConfigurationPage = AssistantConfigurationPage;
4520
+ exports.AssistantVoiceFields = AssistantVoiceFields;
4521
+ exports.AssistantWorkspaceFields = AssistantWorkspaceFields;
4522
+ return module.exports;
4523
+ }
4524
+ });
4525
+
4526
+ //# sourceMappingURL=client.assistant-configuration-view.js.map