@walkhi/code-relax 0.1.0-beta.4 → 0.1.0-beta.40

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 (843) hide show
  1. package/README.md +83 -71
  2. package/dist/bin/cleanup-accounts.mjs +63 -0
  3. package/dist/bin/codex-remote.mjs +323 -184
  4. package/dist/bin/manage-relay-admin.mjs +20 -0
  5. package/dist/bin/migrate-relay-db.mjs +109 -0
  6. package/dist/bin/publish-android-release.mjs +63 -0
  7. package/dist/bin/publish-harmony-release.mjs +46 -0
  8. package/dist/bin/release-upload-server.mjs +174 -0
  9. package/dist/bin/self-relay-admin.mjs +8 -0
  10. package/dist/bin/self-relay-demo.mjs +2 -2
  11. package/dist/bin/self-relay-server.mjs +43 -30
  12. package/dist/build-platform.json +1 -0
  13. package/dist/get-shared-service-url.ps1 +6 -6
  14. package/dist/shared/app-server-events.cjs +368 -238
  15. package/dist/shared/code-browser.cjs +159 -0
  16. package/dist/shared/file-changes.cjs +31 -0
  17. package/dist/shared/file-citations.cjs +117 -0
  18. package/dist/shared/file-mentions.cjs +92 -0
  19. package/dist/shared/message-delivery.cjs +100 -0
  20. package/dist/shared/p2p-data-channel.cjs +22 -0
  21. package/dist/shared/p2p-file-channel.cjs +84 -0
  22. package/dist/shared/plan-pricing.cjs +32 -0
  23. package/dist/shared/question-attention.cjs +69 -0
  24. package/dist/shared/server-requests.cjs +157 -5
  25. package/dist/shared/service-endpoints.cjs +21 -0
  26. package/dist/shared/timeline-sync.cjs +111 -5
  27. package/dist/skills/code-relax/SKILL.md +13 -8
  28. package/dist/src/archived-threads.mjs +22 -0
  29. package/dist/src/bounded-process-log.mjs +63 -0
  30. package/dist/src/celsius-client.mjs +107 -0
  31. package/dist/src/code-browser.mjs +100 -0
  32. package/dist/src/desktop-launcher.mjs +33 -38
  33. package/dist/src/dots-approval-timeline.mjs +99 -0
  34. package/dist/src/dots-avatar.mjs +42 -0
  35. package/dist/src/dots-chat-projection.mjs +50 -0
  36. package/dist/src/dots-chat-server.mjs +482 -0
  37. package/dist/src/dots-message-images.mjs +319 -0
  38. package/dist/src/dots-profile.mjs +77 -0
  39. package/dist/src/dots-room-activity.mjs +94 -0
  40. package/dist/src/dots-room-client.mjs +135 -0
  41. package/dist/src/dots-room-projection.mjs +94 -0
  42. package/dist/src/dots-schedules.mjs +66 -0
  43. package/dist/src/dots-summary-tasks.mjs +32 -0
  44. package/dist/src/dots-task-catalog.mjs +430 -0
  45. package/dist/src/dots-task-files.mjs +52 -0
  46. package/dist/src/file-transfer-range.mjs +22 -0
  47. package/dist/src/file-upload-session.mjs +130 -0
  48. package/dist/src/hosted-history-app-server.mjs +103 -0
  49. package/dist/src/hosted-history-cache.mjs +113 -0
  50. package/dist/src/lan-socket-server.mjs +241 -84
  51. package/dist/src/managed-app-server.mjs +54 -16
  52. package/dist/src/message-images.mjs +0 -18
  53. package/dist/src/message-upload-files.mjs +109 -0
  54. package/dist/src/onboarding.mjs +10 -29
  55. package/dist/src/plan-pricing.mjs +47 -0
  56. package/dist/src/platform/README.md +2 -2
  57. package/dist/src/platform/codex-protocol.mjs +46 -0
  58. package/dist/src/platform/codex-runtime.mjs +3 -0
  59. package/dist/src/platform/credential-store.mjs +3 -0
  60. package/dist/src/platform/desktop-host.mjs +3 -0
  61. package/dist/src/platform/desktop-project-registration.mjs +3 -0
  62. package/dist/src/platform/desktop-project-store.mjs +208 -0
  63. package/dist/src/platform/file-opener.mjs +9 -0
  64. package/dist/src/platform/linux/codex-runtime.mjs +56 -0
  65. package/dist/src/platform/linux/credential-store.mjs +28 -0
  66. package/dist/src/platform/linux/desktop-host.mjs +83 -0
  67. package/dist/src/platform/linux/desktop-installation.mjs +36 -0
  68. package/dist/src/platform/linux/desktop-project-registration.mjs +11 -0
  69. package/dist/src/platform/linux/network-info.mjs +11 -0
  70. package/dist/src/platform/linux/process.mjs +42 -0
  71. package/dist/src/platform/linux/project-locations.mjs +17 -0
  72. package/dist/src/platform/linux/resident-startup.mjs +52 -0
  73. package/dist/src/platform/linux/retained-runtime.mjs +76 -0
  74. package/dist/src/platform/linux/service-host.mjs +18 -0
  75. package/dist/src/platform/linux/system.mjs +49 -0
  76. package/dist/src/platform/linux/user-environment.mjs +7 -0
  77. package/dist/src/platform/macos/codex-runtime.mjs +125 -0
  78. package/dist/src/platform/macos/credential-store.mjs +10 -0
  79. package/dist/src/platform/macos/desktop-host.mjs +82 -0
  80. package/dist/src/platform/macos/desktop-project-registration.mjs +11 -0
  81. package/dist/src/platform/macos/network-info.mjs +8 -0
  82. package/dist/src/platform/macos/project-locations.mjs +8 -0
  83. package/dist/src/platform/macos/resident-startup.mjs +84 -0
  84. package/dist/src/platform/macos/retained-runtime.mjs +127 -0
  85. package/dist/src/platform/macos/service-host.mjs +18 -0
  86. package/dist/src/platform/macos/system.mjs +29 -0
  87. package/dist/src/platform/macos/user-environment.mjs +7 -0
  88. package/dist/src/platform/native-package.mjs +58 -0
  89. package/dist/src/platform/network-info.mjs +3 -0
  90. package/dist/src/platform/path-identity.mjs +5 -0
  91. package/dist/src/platform/posix/network-candidates.mjs +23 -0
  92. package/dist/src/platform/posix/runtime-paths.mjs +25 -0
  93. package/dist/src/platform/power-inhibitor.mjs +22 -0
  94. package/dist/src/platform/project-locations.mjs +3 -0
  95. package/dist/src/platform/runtime-paths.mjs +8 -0
  96. package/dist/src/platform/select.mjs +5 -0
  97. package/dist/src/platform/service-host.mjs +3 -0
  98. package/dist/src/platform/user-environment.mjs +3 -0
  99. package/dist/src/platform/windows/activate-desktop.ps1 +48 -0
  100. package/dist/src/platform/windows/codex-runtime.mjs +215 -0
  101. package/dist/src/platform/windows/credential-store.mjs +27 -27
  102. package/dist/src/platform/windows/credential-store.ps1 +49 -49
  103. package/dist/src/platform/windows/desktop-helper.mjs +179 -0
  104. package/dist/src/platform/windows/desktop-host.mjs +43 -23
  105. package/dist/src/platform/windows/desktop-info.ps1 +14 -16
  106. package/dist/src/platform/windows/desktop-project-registration.mjs +4 -0
  107. package/dist/src/platform/windows/desktop-runtime.ps1 +10 -0
  108. package/dist/src/platform/windows/desktop-shortcuts.ps1 +21 -28
  109. package/dist/src/platform/windows/dots-profile.mjs +49 -0
  110. package/dist/src/platform/windows/keep-awake.ps1 +20 -0
  111. package/dist/src/platform/windows/launch-worker.mjs +17 -18
  112. package/dist/src/platform/windows/project-locations.mjs +16 -0
  113. package/dist/src/platform/windows/resident-start.js +20 -0
  114. package/dist/src/platform/windows/resident-startup.ps1 +76 -20
  115. package/dist/src/platform/windows/rustdesk-video-probe.mjs +168 -0
  116. package/dist/src/platform/windows/service-host.mjs +24 -36
  117. package/dist/src/platform/windows/stop-desktop.ps1 +7 -4
  118. package/dist/src/platform/windows/user-environment.mjs +1 -1
  119. package/dist/src/project-create.mjs +121 -0
  120. package/dist/src/project-management.mjs +59 -0
  121. package/dist/src/project-remove.mjs +55 -0
  122. package/dist/src/project-update.mjs +59 -0
  123. package/dist/src/runtime-discovery.mjs +47 -0
  124. package/dist/src/self-relay/account-cleanup.mjs +30 -0
  125. package/dist/src/self-relay/account-connections.mjs +52 -0
  126. package/dist/src/self-relay/admin-cloud.mjs +101 -0
  127. package/dist/src/self-relay/admin-page.mjs +80 -0
  128. package/dist/src/self-relay/admin-server.mjs +74 -0
  129. package/dist/src/self-relay/admin-state.mjs +85 -19
  130. package/dist/src/self-relay/admin-style.mjs +14 -0
  131. package/dist/src/self-relay/android-releases.mjs +61 -0
  132. package/dist/src/self-relay/client.mjs +45 -45
  133. package/dist/src/self-relay/connector.mjs +421 -144
  134. package/dist/src/self-relay/demo.mjs +34 -35
  135. package/dist/src/self-relay/device-info.mjs +27 -0
  136. package/dist/src/self-relay/device-store.mjs +10 -2
  137. package/dist/src/self-relay/diagnostics.mjs +54 -0
  138. package/dist/src/self-relay/email-accounts.mjs +183 -0
  139. package/dist/src/self-relay/file-wire.mjs +13 -0
  140. package/dist/src/self-relay/lan-authorizations.mjs +44 -2
  141. package/dist/src/self-relay/lifecycle.mjs +250 -272
  142. package/dist/src/self-relay/long-pairing.mjs +40 -0
  143. package/dist/src/self-relay/mysql-state.mjs +118 -0
  144. package/dist/src/self-relay/p2p-probe.mjs +41 -5
  145. package/dist/src/self-relay/privacy-page.mjs +26 -0
  146. package/dist/src/self-relay/public-releases.mjs +52 -0
  147. package/dist/src/self-relay/rtc-file-bridge.mjs +41 -0
  148. package/dist/src/self-relay/server.mjs +652 -114
  149. package/dist/src/self-relay/usage-daily.mjs +55 -0
  150. package/dist/src/self-relay/usage-meter.mjs +141 -0
  151. package/dist/src/server.mjs +1519 -1793
  152. package/dist/src/service-control.mjs +17 -7
  153. package/dist/src/service-doctor.mjs +86 -33
  154. package/dist/src/service-health.mjs +2 -2
  155. package/dist/src/service-install.mjs +91 -85
  156. package/dist/src/service-lifecycle.mjs +117 -108
  157. package/dist/src/service-power.mjs +58 -0
  158. package/dist/src/service-restart.mjs +43 -10
  159. package/dist/src/service-update-worker.mjs +42 -0
  160. package/dist/src/service-update.mjs +71 -0
  161. package/dist/src/shared-app-server.mjs +561 -407
  162. package/dist/src/shared-catalog.mjs +95 -13
  163. package/dist/src/shared-recovery.mjs +15 -11
  164. package/dist/src/shared-thread-stream.mjs +147 -63
  165. package/dist/src/skill-install.mjs +0 -13
  166. package/dist/src/turn-model-history.mjs +47 -0
  167. package/dist/src/usage-reset.mjs +17 -0
  168. package/dist/src/workstation-downloads.mjs +120 -0
  169. package/package.json +35 -37
  170. package/tools/postinstall.mjs +86 -22
  171. package/dist/src/app-server-client.mjs +0 -468
  172. package/dist/src/app-server-tasks.mjs +0 -358
  173. package/dist/src/debug-ui.mjs +0 -131
  174. package/dist/src/platform/windows/desktop-monitor.mjs +0 -34
  175. package/dist/src/platform/windows/desktop-shortcut-run.ps1 +0 -5
  176. package/dist/src/platform/windows/desktop-tools.mjs +0 -48
  177. package/dist/src/thread-catalog.mjs +0 -47
  178. package/dist/tools/find-desktop-pipe.mjs +0 -10
  179. package/dist/web/activity-view.js +0 -283
  180. package/dist/web/approval-ui.js +0 -188
  181. package/dist/web/capabilities.js +0 -57
  182. package/dist/web/chat-transport.js +0 -202
  183. package/dist/web/chat.css +0 -782
  184. package/dist/web/chat.js +0 -3830
  185. package/dist/web/client-platform.js +0 -11
  186. package/dist/web/community-view.js +0 -20
  187. package/dist/web/community.css +0 -77
  188. package/dist/web/community.html +0 -37
  189. package/dist/web/composer-controller.js +0 -101
  190. package/dist/web/conversation-controller.js +0 -99
  191. package/dist/web/disclosure-state-controller.js +0 -95
  192. package/dist/web/draft-controller.js +0 -103
  193. package/dist/web/harmony-platform.js +0 -52
  194. package/dist/web/highlight-powershell.min.js +0 -39
  195. package/dist/web/highlight.min.js +0 -1262
  196. package/dist/web/history-cache.js +0 -240
  197. package/dist/web/history-controller.js +0 -167
  198. package/dist/web/index.html +0 -325
  199. package/dist/web/lan-session.js +0 -122
  200. package/dist/web/link-action-controller.js +0 -212
  201. package/dist/web/message-send-controller.js +0 -177
  202. package/dist/web/message-view.js +0 -98
  203. package/dist/web/p2p-data-channel.js +0 -50
  204. package/dist/web/p2p-diagnostics.js +0 -28
  205. package/dist/web/p2p-probe.js +0 -123
  206. package/dist/web/page-resume.js +0 -28
  207. package/dist/web/pending-message-store.js +0 -108
  208. package/dist/web/queue-controller.js +0 -82
  209. package/dist/web/resources.json +0 -1
  210. package/dist/web/self-relay-session.js +0 -129
  211. package/dist/web/server-requests.js +0 -67
  212. package/dist/web/station-connection-controller.js +0 -75
  213. package/dist/web/task-list-view.js +0 -296
  214. package/dist/web/thread-attention-controller.js +0 -124
  215. package/dist/web/thread-context-controller.js +0 -30
  216. package/dist/web/thread-list-controller.js +0 -61
  217. package/dist/web/thread-list-sync.js +0 -86
  218. package/dist/web/thread-title-controller.js +0 -57
  219. package/dist/web/timeline-formatters.js +0 -249
  220. package/dist/web/timeline-reducer.js +0 -81
  221. package/dist/web/timeline-renderer.js +0 -161
  222. package/dist/web/timeline-scroll-controller.js +0 -50
  223. package/dist/web/timeline-sync.js +0 -67
  224. package/dist/web/usage-controller.js +0 -258
  225. package/dist/web/vendor/README.md +0 -14
  226. package/dist/web/vendor/highlight-powershell.min.js +0 -39
  227. package/dist/web/vendor/highlight.LICENSE.txt +0 -29
  228. package/dist/web/vendor/highlight.min.js +0 -1262
  229. package/dist/web/vendor/lucide.LICENSE.txt +0 -17
  230. package/node_modules/@img/colour/LICENSE.md +0 -82
  231. package/node_modules/@img/colour/README.md +0 -15
  232. package/node_modules/@img/colour/color.cjs +0 -1596
  233. package/node_modules/@img/colour/index.cjs +0 -1
  234. package/node_modules/@img/colour/index.d.ts +0 -929
  235. package/node_modules/@img/colour/package.json +0 -58
  236. package/node_modules/@img/sharp-win32-x64/LICENSE +0 -191
  237. package/node_modules/@img/sharp-win32-x64/README.md +0 -59
  238. package/node_modules/@img/sharp-win32-x64/index.cjs +0 -1
  239. package/node_modules/@img/sharp-win32-x64/lib/libvips-42.dll +0 -0
  240. package/node_modules/@img/sharp-win32-x64/lib/libvips-cpp-8.18.6.dll +0 -0
  241. package/node_modules/@img/sharp-win32-x64/lib/sharp-win32-x64-0.35.4.node +0 -0
  242. package/node_modules/@img/sharp-win32-x64/package.json +0 -40
  243. package/node_modules/@img/sharp-win32-x64/versions.json +0 -30
  244. package/node_modules/@node-datachannel/win32-x64-msvc/node_datachannel.node +0 -0
  245. package/node_modules/@node-datachannel/win32-x64-msvc/package.json +0 -20
  246. package/node_modules/ansi-regex/index.d.ts +0 -37
  247. package/node_modules/ansi-regex/index.js +0 -10
  248. package/node_modules/ansi-regex/license +0 -9
  249. package/node_modules/ansi-regex/package.json +0 -55
  250. package/node_modules/ansi-regex/readme.md +0 -78
  251. package/node_modules/ansi-styles/index.d.ts +0 -345
  252. package/node_modules/ansi-styles/index.js +0 -163
  253. package/node_modules/ansi-styles/license +0 -9
  254. package/node_modules/ansi-styles/package.json +0 -56
  255. package/node_modules/ansi-styles/readme.md +0 -152
  256. package/node_modules/camelcase/index.d.ts +0 -63
  257. package/node_modules/camelcase/index.js +0 -76
  258. package/node_modules/camelcase/license +0 -9
  259. package/node_modules/camelcase/package.json +0 -43
  260. package/node_modules/camelcase/readme.md +0 -99
  261. package/node_modules/cliui/CHANGELOG.md +0 -76
  262. package/node_modules/cliui/LICENSE.txt +0 -14
  263. package/node_modules/cliui/README.md +0 -115
  264. package/node_modules/cliui/index.js +0 -354
  265. package/node_modules/cliui/package.json +0 -65
  266. package/node_modules/color-convert/CHANGELOG.md +0 -54
  267. package/node_modules/color-convert/LICENSE +0 -21
  268. package/node_modules/color-convert/README.md +0 -68
  269. package/node_modules/color-convert/conversions.js +0 -839
  270. package/node_modules/color-convert/index.js +0 -81
  271. package/node_modules/color-convert/package.json +0 -48
  272. package/node_modules/color-convert/route.js +0 -97
  273. package/node_modules/color-name/LICENSE +0 -8
  274. package/node_modules/color-name/README.md +0 -11
  275. package/node_modules/color-name/index.js +0 -152
  276. package/node_modules/color-name/package.json +0 -28
  277. package/node_modules/decamelize/index.js +0 -13
  278. package/node_modules/decamelize/license +0 -21
  279. package/node_modules/decamelize/package.json +0 -38
  280. package/node_modules/decamelize/readme.md +0 -48
  281. package/node_modules/detect-libc/LICENSE +0 -201
  282. package/node_modules/detect-libc/README.md +0 -163
  283. package/node_modules/detect-libc/index.d.ts +0 -14
  284. package/node_modules/detect-libc/lib/detect-libc.js +0 -313
  285. package/node_modules/detect-libc/lib/elf.js +0 -39
  286. package/node_modules/detect-libc/lib/filesystem.js +0 -51
  287. package/node_modules/detect-libc/lib/process.js +0 -24
  288. package/node_modules/detect-libc/package.json +0 -44
  289. package/node_modules/dijkstrajs/.travis.yml +0 -4
  290. package/node_modules/dijkstrajs/CONTRIBUTING.md +0 -8
  291. package/node_modules/dijkstrajs/LICENSE.md +0 -19
  292. package/node_modules/dijkstrajs/README.md +0 -18
  293. package/node_modules/dijkstrajs/dijkstra.js +0 -165
  294. package/node_modules/dijkstrajs/package.json +0 -60
  295. package/node_modules/dijkstrajs/test/dijkstra.test.js +0 -96
  296. package/node_modules/emoji-regex/LICENSE-MIT.txt +0 -20
  297. package/node_modules/emoji-regex/README.md +0 -73
  298. package/node_modules/emoji-regex/es2015/index.js +0 -6
  299. package/node_modules/emoji-regex/es2015/text.js +0 -6
  300. package/node_modules/emoji-regex/index.d.ts +0 -23
  301. package/node_modules/emoji-regex/index.js +0 -6
  302. package/node_modules/emoji-regex/package.json +0 -50
  303. package/node_modules/emoji-regex/text.js +0 -6
  304. package/node_modules/find-up/index.d.ts +0 -137
  305. package/node_modules/find-up/index.js +0 -89
  306. package/node_modules/find-up/license +0 -9
  307. package/node_modules/find-up/package.json +0 -53
  308. package/node_modules/find-up/readme.md +0 -156
  309. package/node_modules/get-caller-file/LICENSE.md +0 -6
  310. package/node_modules/get-caller-file/README.md +0 -41
  311. package/node_modules/get-caller-file/index.d.ts +0 -2
  312. package/node_modules/get-caller-file/index.js +0 -22
  313. package/node_modules/get-caller-file/index.js.map +0 -1
  314. package/node_modules/get-caller-file/package.json +0 -42
  315. package/node_modules/is-fullwidth-code-point/index.d.ts +0 -17
  316. package/node_modules/is-fullwidth-code-point/index.js +0 -50
  317. package/node_modules/is-fullwidth-code-point/license +0 -9
  318. package/node_modules/is-fullwidth-code-point/package.json +0 -42
  319. package/node_modules/is-fullwidth-code-point/readme.md +0 -39
  320. package/node_modules/locate-path/index.d.ts +0 -83
  321. package/node_modules/locate-path/index.js +0 -65
  322. package/node_modules/locate-path/license +0 -9
  323. package/node_modules/locate-path/package.json +0 -45
  324. package/node_modules/locate-path/readme.md +0 -122
  325. package/node_modules/node-datachannel/.clang-format +0 -17
  326. package/node_modules/node-datachannel/.editorconfig +0 -12
  327. package/node_modules/node-datachannel/.eslintignore +0 -8
  328. package/node_modules/node-datachannel/.eslintrc.json +0 -27
  329. package/node_modules/node-datachannel/.gitmodules +0 -3
  330. package/node_modules/node-datachannel/.prettierignore +0 -7
  331. package/node_modules/node-datachannel/.prettierrc +0 -13
  332. package/node_modules/node-datachannel/API.md +0 -247
  333. package/node_modules/node-datachannel/BULDING.md +0 -26
  334. package/node_modules/node-datachannel/CMakeLists.txt +0 -153
  335. package/node_modules/node-datachannel/LICENSE +0 -373
  336. package/node_modules/node-datachannel/README.md +0 -129
  337. package/node_modules/node-datachannel/dist/cjs/index.cjs +0 -11
  338. package/node_modules/node-datachannel/dist/cjs/index.cjs.map +0 -1
  339. package/node_modules/node-datachannel/dist/cjs/lib/datachannel-stream.cjs +0 -101
  340. package/node_modules/node-datachannel/dist/cjs/lib/datachannel-stream.cjs.map +0 -1
  341. package/node_modules/node-datachannel/dist/cjs/lib/index.cjs +0 -91
  342. package/node_modules/node-datachannel/dist/cjs/lib/index.cjs.map +0 -1
  343. package/node_modules/node-datachannel/dist/cjs/lib/node-datachannel.cjs +0 -94
  344. package/node_modules/node-datachannel/dist/cjs/lib/node-datachannel.cjs.map +0 -1
  345. package/node_modules/node-datachannel/dist/cjs/lib/websocket-server.cjs +0 -45
  346. package/node_modules/node-datachannel/dist/cjs/lib/websocket-server.cjs.map +0 -1
  347. package/node_modules/node-datachannel/dist/cjs/lib/websocket.cjs +0 -8
  348. package/node_modules/node-datachannel/dist/cjs/lib/websocket.cjs.map +0 -1
  349. package/node_modules/node-datachannel/dist/cjs/polyfill/Events.cjs +0 -86
  350. package/node_modules/node-datachannel/dist/cjs/polyfill/Events.cjs.map +0 -1
  351. package/node_modules/node-datachannel/dist/cjs/polyfill/Exception.cjs +0 -34
  352. package/node_modules/node-datachannel/dist/cjs/polyfill/Exception.cjs.map +0 -1
  353. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCCertificate.cjs +0 -34
  354. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCCertificate.cjs.map +0 -1
  355. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDataChannel.cjs +0 -214
  356. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDataChannel.cjs.map +0 -1
  357. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDtlsTransport.cjs +0 -53
  358. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDtlsTransport.cjs.map +0 -1
  359. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCError.cjs +0 -83
  360. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCError.cjs.map +0 -1
  361. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceCandidate.cjs +0 -132
  362. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceCandidate.cjs.map +0 -1
  363. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceTransport.cjs +0 -94
  364. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceTransport.cjs.map +0 -1
  365. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCPeerConnection.cjs +0 -434
  366. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCPeerConnection.cjs.map +0 -1
  367. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSctpTransport.cjs +0 -59
  368. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSctpTransport.cjs.map +0 -1
  369. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSessionDescription.cjs +0 -45
  370. package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSessionDescription.cjs.map +0 -1
  371. package/node_modules/node-datachannel/dist/cjs/polyfill/index.cjs +0 -42
  372. package/node_modules/node-datachannel/dist/cjs/polyfill/index.cjs.map +0 -1
  373. package/node_modules/node-datachannel/dist/esm/index.mjs +0 -8
  374. package/node_modules/node-datachannel/dist/esm/index.mjs.map +0 -1
  375. package/node_modules/node-datachannel/dist/esm/lib/datachannel-stream.mjs +0 -78
  376. package/node_modules/node-datachannel/dist/esm/lib/datachannel-stream.mjs.map +0 -1
  377. package/node_modules/node-datachannel/dist/esm/lib/index.mjs +0 -64
  378. package/node_modules/node-datachannel/dist/esm/lib/index.mjs.map +0 -1
  379. package/node_modules/node-datachannel/dist/esm/lib/node-datachannel.mjs +0 -78
  380. package/node_modules/node-datachannel/dist/esm/lib/node-datachannel.mjs.map +0 -1
  381. package/node_modules/node-datachannel/dist/esm/lib/websocket-server.mjs +0 -43
  382. package/node_modules/node-datachannel/dist/esm/lib/websocket-server.mjs.map +0 -1
  383. package/node_modules/node-datachannel/dist/esm/lib/websocket.mjs +0 -6
  384. package/node_modules/node-datachannel/dist/esm/lib/websocket.mjs.map +0 -1
  385. package/node_modules/node-datachannel/dist/esm/polyfill/Events.mjs +0 -82
  386. package/node_modules/node-datachannel/dist/esm/polyfill/Events.mjs.map +0 -1
  387. package/node_modules/node-datachannel/dist/esm/polyfill/Exception.mjs +0 -28
  388. package/node_modules/node-datachannel/dist/esm/polyfill/Exception.mjs.map +0 -1
  389. package/node_modules/node-datachannel/dist/esm/polyfill/RTCCertificate.mjs +0 -30
  390. package/node_modules/node-datachannel/dist/esm/polyfill/RTCCertificate.mjs.map +0 -1
  391. package/node_modules/node-datachannel/dist/esm/polyfill/RTCDataChannel.mjs +0 -210
  392. package/node_modules/node-datachannel/dist/esm/polyfill/RTCDataChannel.mjs.map +0 -1
  393. package/node_modules/node-datachannel/dist/esm/polyfill/RTCDtlsTransport.mjs +0 -49
  394. package/node_modules/node-datachannel/dist/esm/polyfill/RTCDtlsTransport.mjs.map +0 -1
  395. package/node_modules/node-datachannel/dist/esm/polyfill/RTCError.mjs +0 -79
  396. package/node_modules/node-datachannel/dist/esm/polyfill/RTCError.mjs.map +0 -1
  397. package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceCandidate.mjs +0 -128
  398. package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceCandidate.mjs.map +0 -1
  399. package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceTransport.mjs +0 -89
  400. package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceTransport.mjs.map +0 -1
  401. package/node_modules/node-datachannel/dist/esm/polyfill/RTCPeerConnection.mjs +0 -430
  402. package/node_modules/node-datachannel/dist/esm/polyfill/RTCPeerConnection.mjs.map +0 -1
  403. package/node_modules/node-datachannel/dist/esm/polyfill/RTCSctpTransport.mjs +0 -55
  404. package/node_modules/node-datachannel/dist/esm/polyfill/RTCSctpTransport.mjs.map +0 -1
  405. package/node_modules/node-datachannel/dist/esm/polyfill/RTCSessionDescription.mjs +0 -41
  406. package/node_modules/node-datachannel/dist/esm/polyfill/RTCSessionDescription.mjs.map +0 -1
  407. package/node_modules/node-datachannel/dist/esm/polyfill/index.mjs +0 -27
  408. package/node_modules/node-datachannel/dist/esm/polyfill/index.mjs.map +0 -1
  409. package/node_modules/node-datachannel/dist/types/lib/datachannel-stream.d.ts +0 -24
  410. package/node_modules/node-datachannel/dist/types/lib/index.d.ts +0 -241
  411. package/node_modules/node-datachannel/dist/types/lib/types.d.ts +0 -119
  412. package/node_modules/node-datachannel/dist/types/lib/websocket-server.d.ts +0 -13
  413. package/node_modules/node-datachannel/dist/types/lib/websocket.d.ts +0 -25
  414. package/node_modules/node-datachannel/dist/types/polyfill/Events.d.ts +0 -15
  415. package/node_modules/node-datachannel/dist/types/polyfill/RTCCertificate.d.ts +0 -9
  416. package/node_modules/node-datachannel/dist/types/polyfill/RTCDataChannel.d.ts +0 -29
  417. package/node_modules/node-datachannel/dist/types/polyfill/RTCDtlsTransport.d.ts +0 -15
  418. package/node_modules/node-datachannel/dist/types/polyfill/RTCError.d.ts +0 -17
  419. package/node_modules/node-datachannel/dist/types/polyfill/RTCIceCandidate.d.ts +0 -21
  420. package/node_modules/node-datachannel/dist/types/polyfill/RTCIceTransport.d.ts +0 -30
  421. package/node_modules/node-datachannel/dist/types/polyfill/RTCPeerConnection.d.ts +0 -64
  422. package/node_modules/node-datachannel/dist/types/polyfill/RTCSctpTransport.d.ts +0 -15
  423. package/node_modules/node-datachannel/dist/types/polyfill/RTCSessionDescription.d.ts +0 -10
  424. package/node_modules/node-datachannel/dist/types/polyfill/index.d.ts +0 -26
  425. package/node_modules/node-datachannel/jest.config.ts +0 -14
  426. package/node_modules/node-datachannel/package.json +0 -133
  427. package/node_modules/node-datachannel/rollup.config.mjs +0 -67
  428. package/node_modules/node-datachannel/src/cpp/data-channel-wrapper.cpp +0 -530
  429. package/node_modules/node-datachannel/src/cpp/data-channel-wrapper.h +0 -63
  430. package/node_modules/node-datachannel/src/cpp/ice-udp-mux-listener-wrapper.cpp +0 -172
  431. package/node_modules/node-datachannel/src/cpp/ice-udp-mux-listener-wrapper.h +0 -43
  432. package/node_modules/node-datachannel/src/cpp/main.cpp +0 -58
  433. package/node_modules/node-datachannel/src/cpp/media-audio-wrapper.cpp +0 -457
  434. package/node_modules/node-datachannel/src/cpp/media-audio-wrapper.h +0 -52
  435. package/node_modules/node-datachannel/src/cpp/media-av1packetization.cpp +0 -24
  436. package/node_modules/node-datachannel/src/cpp/media-av1packetization.h +0 -11
  437. package/node_modules/node-datachannel/src/cpp/media-av1rtppacketizer-wrapper.cpp +0 -126
  438. package/node_modules/node-datachannel/src/cpp/media-av1rtppacketizer-wrapper.h +0 -29
  439. package/node_modules/node-datachannel/src/cpp/media-direction.cpp +0 -43
  440. package/node_modules/node-datachannel/src/cpp/media-direction.h +0 -10
  441. package/node_modules/node-datachannel/src/cpp/media-h264rtppacketizer-wrapper.cpp +0 -126
  442. package/node_modules/node-datachannel/src/cpp/media-h264rtppacketizer-wrapper.h +0 -30
  443. package/node_modules/node-datachannel/src/cpp/media-h265rtppacketizer-wrapper.cpp +0 -126
  444. package/node_modules/node-datachannel/src/cpp/media-h265rtppacketizer-wrapper.h +0 -30
  445. package/node_modules/node-datachannel/src/cpp/media-h26xseparator.cpp +0 -32
  446. package/node_modules/node-datachannel/src/cpp/media-h26xseparator.h +0 -11
  447. package/node_modules/node-datachannel/src/cpp/media-mediahandler-helper.cpp +0 -31
  448. package/node_modules/node-datachannel/src/cpp/media-mediahandler-helper.h +0 -11
  449. package/node_modules/node-datachannel/src/cpp/media-pacinghandler-wrapper.cpp +0 -79
  450. package/node_modules/node-datachannel/src/cpp/media-pacinghandler-wrapper.h +0 -28
  451. package/node_modules/node-datachannel/src/cpp/media-rtcpnackresponder-wrapper.cpp +0 -68
  452. package/node_modules/node-datachannel/src/cpp/media-rtcpnackresponder-wrapper.h +0 -28
  453. package/node_modules/node-datachannel/src/cpp/media-rtcpreceivingsession-wrapper.cpp +0 -57
  454. package/node_modules/node-datachannel/src/cpp/media-rtcpreceivingsession-wrapper.h +0 -28
  455. package/node_modules/node-datachannel/src/cpp/media-rtcpsrreporter-wrapper.cpp +0 -93
  456. package/node_modules/node-datachannel/src/cpp/media-rtcpsrreporter-wrapper.h +0 -30
  457. package/node_modules/node-datachannel/src/cpp/media-rtppacketizationconfig-wrapper.cpp +0 -167
  458. package/node_modules/node-datachannel/src/cpp/media-rtppacketizationconfig-wrapper.h +0 -35
  459. package/node_modules/node-datachannel/src/cpp/media-rtppacketizer-wrapper.cpp +0 -95
  460. package/node_modules/node-datachannel/src/cpp/media-rtppacketizer-wrapper.h +0 -30
  461. package/node_modules/node-datachannel/src/cpp/media-track-wrapper.cpp +0 -458
  462. package/node_modules/node-datachannel/src/cpp/media-track-wrapper.h +0 -61
  463. package/node_modules/node-datachannel/src/cpp/media-video-wrapper.cpp +0 -526
  464. package/node_modules/node-datachannel/src/cpp/media-video-wrapper.h +0 -56
  465. package/node_modules/node-datachannel/src/cpp/peer-connection-wrapper.cpp +0 -1298
  466. package/node_modules/node-datachannel/src/cpp/peer-connection-wrapper.h +0 -89
  467. package/node_modules/node-datachannel/src/cpp/rtc-wrapper.cpp +0 -205
  468. package/node_modules/node-datachannel/src/cpp/rtc-wrapper.h +0 -24
  469. package/node_modules/node-datachannel/src/cpp/thread-safe-callback.cpp +0 -57
  470. package/node_modules/node-datachannel/src/cpp/thread-safe-callback.h +0 -47
  471. package/node_modules/node-datachannel/src/cpp/web-socket-server-wrapper.cpp +0 -275
  472. package/node_modules/node-datachannel/src/cpp/web-socket-server-wrapper.h +0 -41
  473. package/node_modules/node-datachannel/src/cpp/web-socket-wrapper.cpp +0 -796
  474. package/node_modules/node-datachannel/src/cpp/web-socket-wrapper.h +0 -63
  475. package/node_modules/node-datachannel/src/index.ts +0 -9
  476. package/node_modules/node-datachannel/src/lib/datachannel-stream.ts +0 -100
  477. package/node_modules/node-datachannel/src/lib/index.ts +0 -290
  478. package/node_modules/node-datachannel/src/lib/node-datachannel.ts +0 -84
  479. package/node_modules/node-datachannel/src/lib/types.ts +0 -169
  480. package/node_modules/node-datachannel/src/lib/websocket-server.ts +0 -37
  481. package/node_modules/node-datachannel/src/lib/websocket.ts +0 -26
  482. package/node_modules/node-datachannel/src/polyfill/Events.ts +0 -82
  483. package/node_modules/node-datachannel/src/polyfill/Exception.ts +0 -37
  484. package/node_modules/node-datachannel/src/polyfill/README.md +0 -41
  485. package/node_modules/node-datachannel/src/polyfill/RTCCertificate.ts +0 -21
  486. package/node_modules/node-datachannel/src/polyfill/RTCDataChannel.ts +0 -225
  487. package/node_modules/node-datachannel/src/polyfill/RTCDtlsTransport.ts +0 -46
  488. package/node_modules/node-datachannel/src/polyfill/RTCError.ts +0 -78
  489. package/node_modules/node-datachannel/src/polyfill/RTCIceCandidate.ts +0 -128
  490. package/node_modules/node-datachannel/src/polyfill/RTCIceTransport.ts +0 -90
  491. package/node_modules/node-datachannel/src/polyfill/RTCPeerConnection.ts +0 -527
  492. package/node_modules/node-datachannel/src/polyfill/RTCSctpTransport.ts +0 -51
  493. package/node_modules/node-datachannel/src/polyfill/RTCSessionDescription.ts +0 -41
  494. package/node_modules/node-datachannel/src/polyfill/index.ts +0 -38
  495. package/node_modules/node-datachannel/tsconfig.json +0 -21
  496. package/node_modules/p-limit/index.d.ts +0 -38
  497. package/node_modules/p-limit/index.js +0 -57
  498. package/node_modules/p-limit/license +0 -9
  499. package/node_modules/p-limit/package.json +0 -52
  500. package/node_modules/p-limit/readme.md +0 -101
  501. package/node_modules/p-locate/index.d.ts +0 -64
  502. package/node_modules/p-locate/index.js +0 -52
  503. package/node_modules/p-locate/license +0 -9
  504. package/node_modules/p-locate/package.json +0 -53
  505. package/node_modules/p-locate/readme.md +0 -90
  506. package/node_modules/p-try/index.d.ts +0 -39
  507. package/node_modules/p-try/index.js +0 -9
  508. package/node_modules/p-try/license +0 -9
  509. package/node_modules/p-try/package.json +0 -42
  510. package/node_modules/p-try/readme.md +0 -58
  511. package/node_modules/path-exists/index.d.ts +0 -28
  512. package/node_modules/path-exists/index.js +0 -23
  513. package/node_modules/path-exists/license +0 -9
  514. package/node_modules/path-exists/package.json +0 -39
  515. package/node_modules/path-exists/readme.md +0 -52
  516. package/node_modules/pngjs/.eslintignore +0 -2
  517. package/node_modules/pngjs/.eslintrc.json +0 -19
  518. package/node_modules/pngjs/.prettierignore +0 -3
  519. package/node_modules/pngjs/LICENSE +0 -20
  520. package/node_modules/pngjs/README.md +0 -429
  521. package/node_modules/pngjs/browser.js +0 -16096
  522. package/node_modules/pngjs/coverage/lcov-report/base.css +0 -354
  523. package/node_modules/pngjs/coverage/lcov-report/bitmapper.js.html +0 -881
  524. package/node_modules/pngjs/coverage/lcov-report/bitpacker.js.html +0 -554
  525. package/node_modules/pngjs/coverage/lcov-report/block-navigation.js +0 -77
  526. package/node_modules/pngjs/coverage/lcov-report/chunkstream.js.html +0 -647
  527. package/node_modules/pngjs/coverage/lcov-report/constants.js.html +0 -176
  528. package/node_modules/pngjs/coverage/lcov-report/crc.js.html +0 -200
  529. package/node_modules/pngjs/coverage/lcov-report/favicon.png +0 -0
  530. package/node_modules/pngjs/coverage/lcov-report/filter-pack.js.html +0 -593
  531. package/node_modules/pngjs/coverage/lcov-report/filter-parse-async.js.html +0 -152
  532. package/node_modules/pngjs/coverage/lcov-report/filter-parse-sync.js.html +0 -143
  533. package/node_modules/pngjs/coverage/lcov-report/filter-parse.js.html +0 -611
  534. package/node_modules/pngjs/coverage/lcov-report/format-normaliser.js.html +0 -359
  535. package/node_modules/pngjs/coverage/lcov-report/index.html +0 -426
  536. package/node_modules/pngjs/coverage/lcov-report/interlace.js.html +0 -365
  537. package/node_modules/pngjs/coverage/lcov-report/packer-async.js.html +0 -230
  538. package/node_modules/pngjs/coverage/lcov-report/packer-sync.js.html +0 -248
  539. package/node_modules/pngjs/coverage/lcov-report/packer.js.html +0 -467
  540. package/node_modules/pngjs/coverage/lcov-report/paeth-predictor.js.html +0 -128
  541. package/node_modules/pngjs/coverage/lcov-report/parser-async.js.html +0 -575
  542. package/node_modules/pngjs/coverage/lcov-report/parser-sync.js.html +0 -404
  543. package/node_modules/pngjs/coverage/lcov-report/parser.js.html +0 -950
  544. package/node_modules/pngjs/coverage/lcov-report/png-sync.js.html +0 -116
  545. package/node_modules/pngjs/coverage/lcov-report/png.js.html +0 -662
  546. package/node_modules/pngjs/coverage/lcov-report/prettify.css +0 -101
  547. package/node_modules/pngjs/coverage/lcov-report/prettify.js +0 -1007
  548. package/node_modules/pngjs/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  549. package/node_modules/pngjs/coverage/lcov-report/sorter.js +0 -169
  550. package/node_modules/pngjs/coverage/lcov-report/sync-inflate.js.html +0 -584
  551. package/node_modules/pngjs/coverage/lcov-report/sync-reader.js.html +0 -215
  552. package/node_modules/pngjs/coverage/lcov.info +0 -2177
  553. package/node_modules/pngjs/lib/bitmapper.js +0 -267
  554. package/node_modules/pngjs/lib/bitpacker.js +0 -158
  555. package/node_modules/pngjs/lib/chunkstream.js +0 -189
  556. package/node_modules/pngjs/lib/constants.js +0 -32
  557. package/node_modules/pngjs/lib/crc.js +0 -40
  558. package/node_modules/pngjs/lib/filter-pack.js +0 -171
  559. package/node_modules/pngjs/lib/filter-parse-async.js +0 -24
  560. package/node_modules/pngjs/lib/filter-parse-sync.js +0 -21
  561. package/node_modules/pngjs/lib/filter-parse.js +0 -177
  562. package/node_modules/pngjs/lib/format-normaliser.js +0 -93
  563. package/node_modules/pngjs/lib/interlace.js +0 -95
  564. package/node_modules/pngjs/lib/packer-async.js +0 -50
  565. package/node_modules/pngjs/lib/packer-sync.js +0 -56
  566. package/node_modules/pngjs/lib/packer.js +0 -129
  567. package/node_modules/pngjs/lib/paeth-predictor.js +0 -16
  568. package/node_modules/pngjs/lib/parser-async.js +0 -165
  569. package/node_modules/pngjs/lib/parser-sync.js +0 -108
  570. package/node_modules/pngjs/lib/parser.js +0 -290
  571. package/node_modules/pngjs/lib/png-sync.js +0 -12
  572. package/node_modules/pngjs/lib/png.js +0 -194
  573. package/node_modules/pngjs/lib/sync-inflate.js +0 -168
  574. package/node_modules/pngjs/lib/sync-reader.js +0 -45
  575. package/node_modules/pngjs/package.json +0 -73
  576. package/node_modules/qrcode/README.md +0 -770
  577. package/node_modules/qrcode/bin/qrcode +0 -159
  578. package/node_modules/qrcode/helper/to-sjis-browser.js +0 -2
  579. package/node_modules/qrcode/helper/to-sjis.js +0 -105
  580. package/node_modules/qrcode/lib/browser.js +0 -76
  581. package/node_modules/qrcode/lib/can-promise.js +0 -7
  582. package/node_modules/qrcode/lib/core/alignment-pattern.js +0 -83
  583. package/node_modules/qrcode/lib/core/alphanumeric-data.js +0 -59
  584. package/node_modules/qrcode/lib/core/bit-buffer.js +0 -37
  585. package/node_modules/qrcode/lib/core/bit-matrix.js +0 -65
  586. package/node_modules/qrcode/lib/core/byte-data.js +0 -30
  587. package/node_modules/qrcode/lib/core/error-correction-code.js +0 -135
  588. package/node_modules/qrcode/lib/core/error-correction-level.js +0 -50
  589. package/node_modules/qrcode/lib/core/finder-pattern.js +0 -22
  590. package/node_modules/qrcode/lib/core/format-info.js +0 -29
  591. package/node_modules/qrcode/lib/core/galois-field.js +0 -69
  592. package/node_modules/qrcode/lib/core/kanji-data.js +0 -54
  593. package/node_modules/qrcode/lib/core/mask-pattern.js +0 -234
  594. package/node_modules/qrcode/lib/core/mode.js +0 -167
  595. package/node_modules/qrcode/lib/core/numeric-data.js +0 -43
  596. package/node_modules/qrcode/lib/core/polynomial.js +0 -62
  597. package/node_modules/qrcode/lib/core/qrcode.js +0 -495
  598. package/node_modules/qrcode/lib/core/reed-solomon-encoder.js +0 -56
  599. package/node_modules/qrcode/lib/core/regex.js +0 -31
  600. package/node_modules/qrcode/lib/core/segments.js +0 -330
  601. package/node_modules/qrcode/lib/core/utils.js +0 -63
  602. package/node_modules/qrcode/lib/core/version-check.js +0 -9
  603. package/node_modules/qrcode/lib/core/version.js +0 -163
  604. package/node_modules/qrcode/lib/index.js +0 -12
  605. package/node_modules/qrcode/lib/renderer/canvas.js +0 -63
  606. package/node_modules/qrcode/lib/renderer/png.js +0 -78
  607. package/node_modules/qrcode/lib/renderer/svg-tag.js +0 -81
  608. package/node_modules/qrcode/lib/renderer/svg.js +0 -19
  609. package/node_modules/qrcode/lib/renderer/terminal/terminal-small.js +0 -85
  610. package/node_modules/qrcode/lib/renderer/terminal/terminal.js +0 -49
  611. package/node_modules/qrcode/lib/renderer/terminal.js +0 -9
  612. package/node_modules/qrcode/lib/renderer/utf8.js +0 -71
  613. package/node_modules/qrcode/lib/renderer/utils.js +0 -99
  614. package/node_modules/qrcode/lib/server.js +0 -138
  615. package/node_modules/qrcode/license +0 -10
  616. package/node_modules/qrcode/package.json +0 -77
  617. package/node_modules/require-directory/.jshintrc +0 -67
  618. package/node_modules/require-directory/.npmignore +0 -1
  619. package/node_modules/require-directory/.travis.yml +0 -3
  620. package/node_modules/require-directory/LICENSE +0 -22
  621. package/node_modules/require-directory/README.markdown +0 -184
  622. package/node_modules/require-directory/index.js +0 -86
  623. package/node_modules/require-directory/package.json +0 -40
  624. package/node_modules/require-main-filename/CHANGELOG.md +0 -26
  625. package/node_modules/require-main-filename/LICENSE.txt +0 -14
  626. package/node_modules/require-main-filename/README.md +0 -26
  627. package/node_modules/require-main-filename/index.js +0 -18
  628. package/node_modules/require-main-filename/package.json +0 -35
  629. package/node_modules/semver/LICENSE +0 -15
  630. package/node_modules/semver/README.md +0 -680
  631. package/node_modules/semver/bin/semver.js +0 -195
  632. package/node_modules/semver/classes/comparator.js +0 -143
  633. package/node_modules/semver/classes/index.js +0 -7
  634. package/node_modules/semver/classes/range.js +0 -577
  635. package/node_modules/semver/classes/semver.js +0 -350
  636. package/node_modules/semver/functions/clean.js +0 -8
  637. package/node_modules/semver/functions/cmp.js +0 -54
  638. package/node_modules/semver/functions/coerce.js +0 -62
  639. package/node_modules/semver/functions/compare-build.js +0 -9
  640. package/node_modules/semver/functions/compare-loose.js +0 -5
  641. package/node_modules/semver/functions/compare.js +0 -7
  642. package/node_modules/semver/functions/diff.js +0 -60
  643. package/node_modules/semver/functions/eq.js +0 -5
  644. package/node_modules/semver/functions/gt.js +0 -5
  645. package/node_modules/semver/functions/gte.js +0 -5
  646. package/node_modules/semver/functions/inc.js +0 -21
  647. package/node_modules/semver/functions/lt.js +0 -5
  648. package/node_modules/semver/functions/lte.js +0 -5
  649. package/node_modules/semver/functions/major.js +0 -5
  650. package/node_modules/semver/functions/minor.js +0 -5
  651. package/node_modules/semver/functions/neq.js +0 -5
  652. package/node_modules/semver/functions/parse.js +0 -18
  653. package/node_modules/semver/functions/patch.js +0 -5
  654. package/node_modules/semver/functions/prerelease.js +0 -8
  655. package/node_modules/semver/functions/rcompare.js +0 -5
  656. package/node_modules/semver/functions/rsort.js +0 -5
  657. package/node_modules/semver/functions/satisfies.js +0 -12
  658. package/node_modules/semver/functions/sort.js +0 -5
  659. package/node_modules/semver/functions/truncate.js +0 -48
  660. package/node_modules/semver/functions/valid.js +0 -8
  661. package/node_modules/semver/index.js +0 -93
  662. package/node_modules/semver/internal/constants.js +0 -37
  663. package/node_modules/semver/internal/debug.js +0 -11
  664. package/node_modules/semver/internal/identifiers.js +0 -29
  665. package/node_modules/semver/internal/lrucache.js +0 -42
  666. package/node_modules/semver/internal/parse-options.js +0 -17
  667. package/node_modules/semver/internal/re.js +0 -223
  668. package/node_modules/semver/package.json +0 -78
  669. package/node_modules/semver/preload.js +0 -4
  670. package/node_modules/semver/range.bnf +0 -17
  671. package/node_modules/semver/ranges/gtr.js +0 -6
  672. package/node_modules/semver/ranges/intersects.js +0 -9
  673. package/node_modules/semver/ranges/ltr.js +0 -6
  674. package/node_modules/semver/ranges/max-satisfying.js +0 -27
  675. package/node_modules/semver/ranges/min-satisfying.js +0 -26
  676. package/node_modules/semver/ranges/min-version.js +0 -63
  677. package/node_modules/semver/ranges/outside.js +0 -82
  678. package/node_modules/semver/ranges/simplify.js +0 -49
  679. package/node_modules/semver/ranges/subset.js +0 -249
  680. package/node_modules/semver/ranges/to-comparators.js +0 -10
  681. package/node_modules/semver/ranges/valid.js +0 -13
  682. package/node_modules/set-blocking/CHANGELOG.md +0 -26
  683. package/node_modules/set-blocking/LICENSE.txt +0 -14
  684. package/node_modules/set-blocking/README.md +0 -31
  685. package/node_modules/set-blocking/index.js +0 -7
  686. package/node_modules/set-blocking/package.json +0 -42
  687. package/node_modules/sharp/LICENSE +0 -191
  688. package/node_modules/sharp/README.md +0 -112
  689. package/node_modules/sharp/dist/channel.cjs +0 -177
  690. package/node_modules/sharp/dist/channel.mjs +0 -177
  691. package/node_modules/sharp/dist/colour.cjs +0 -199
  692. package/node_modules/sharp/dist/colour.mjs +0 -199
  693. package/node_modules/sharp/dist/composite.cjs +0 -213
  694. package/node_modules/sharp/dist/composite.mjs +0 -213
  695. package/node_modules/sharp/dist/constructor.cjs +0 -511
  696. package/node_modules/sharp/dist/constructor.mjs +0 -511
  697. package/node_modules/sharp/dist/index.cjs +0 -25
  698. package/node_modules/sharp/dist/index.d.cts +0 -1999
  699. package/node_modules/sharp/dist/index.d.mts +0 -2046
  700. package/node_modules/sharp/dist/index.mjs +0 -25
  701. package/node_modules/sharp/dist/input.cjs +0 -819
  702. package/node_modules/sharp/dist/input.mjs +0 -819
  703. package/node_modules/sharp/dist/is.cjs +0 -143
  704. package/node_modules/sharp/dist/is.mjs +0 -143
  705. package/node_modules/sharp/dist/libvips.cjs +0 -212
  706. package/node_modules/sharp/dist/libvips.mjs +0 -212
  707. package/node_modules/sharp/dist/operation.cjs +0 -1002
  708. package/node_modules/sharp/dist/operation.mjs +0 -1002
  709. package/node_modules/sharp/dist/output.cjs +0 -1785
  710. package/node_modules/sharp/dist/output.mjs +0 -1785
  711. package/node_modules/sharp/dist/resize.cjs +0 -617
  712. package/node_modules/sharp/dist/resize.mjs +0 -617
  713. package/node_modules/sharp/dist/sharp.cjs +0 -174
  714. package/node_modules/sharp/dist/sharp.mjs +0 -174
  715. package/node_modules/sharp/dist/utility.cjs +0 -301
  716. package/node_modules/sharp/dist/utility.mjs +0 -301
  717. package/node_modules/sharp/install/build.js +0 -38
  718. package/node_modules/sharp/lib/index.d.ts +0 -1999
  719. package/node_modules/sharp/package.json +0 -227
  720. package/node_modules/sharp/src/binding.gyp +0 -303
  721. package/node_modules/sharp/src/common.cc +0 -1186
  722. package/node_modules/sharp/src/common.h +0 -421
  723. package/node_modules/sharp/src/metadata.cc +0 -404
  724. package/node_modules/sharp/src/metadata.h +0 -95
  725. package/node_modules/sharp/src/operations.cc +0 -516
  726. package/node_modules/sharp/src/operations.h +0 -137
  727. package/node_modules/sharp/src/pipeline.cc +0 -1948
  728. package/node_modules/sharp/src/pipeline.h +0 -422
  729. package/node_modules/sharp/src/sharp.cc +0 -43
  730. package/node_modules/sharp/src/stats.cc +0 -186
  731. package/node_modules/sharp/src/stats.h +0 -62
  732. package/node_modules/sharp/src/utilities.cc +0 -289
  733. package/node_modules/sharp/src/utilities.h +0 -22
  734. package/node_modules/string-width/index.d.ts +0 -29
  735. package/node_modules/string-width/index.js +0 -47
  736. package/node_modules/string-width/license +0 -9
  737. package/node_modules/string-width/package.json +0 -56
  738. package/node_modules/string-width/readme.md +0 -50
  739. package/node_modules/strip-ansi/index.d.ts +0 -17
  740. package/node_modules/strip-ansi/index.js +0 -4
  741. package/node_modules/strip-ansi/license +0 -9
  742. package/node_modules/strip-ansi/package.json +0 -54
  743. package/node_modules/strip-ansi/readme.md +0 -46
  744. package/node_modules/which-module/LICENSE +0 -13
  745. package/node_modules/which-module/README.md +0 -58
  746. package/node_modules/which-module/index.js +0 -9
  747. package/node_modules/which-module/package.json +0 -41
  748. package/node_modules/wrap-ansi/index.js +0 -186
  749. package/node_modules/wrap-ansi/license +0 -9
  750. package/node_modules/wrap-ansi/package.json +0 -61
  751. package/node_modules/wrap-ansi/readme.md +0 -97
  752. package/node_modules/ws/LICENSE +0 -20
  753. package/node_modules/ws/README.md +0 -548
  754. package/node_modules/ws/browser.js +0 -8
  755. package/node_modules/ws/index.js +0 -22
  756. package/node_modules/ws/lib/buffer-util.js +0 -131
  757. package/node_modules/ws/lib/constants.js +0 -19
  758. package/node_modules/ws/lib/event-target.js +0 -292
  759. package/node_modules/ws/lib/extension.js +0 -203
  760. package/node_modules/ws/lib/limiter.js +0 -55
  761. package/node_modules/ws/lib/permessage-deflate.js +0 -530
  762. package/node_modules/ws/lib/receiver.js +0 -743
  763. package/node_modules/ws/lib/sender.js +0 -607
  764. package/node_modules/ws/lib/stream.js +0 -161
  765. package/node_modules/ws/lib/subprotocol.js +0 -62
  766. package/node_modules/ws/lib/validation.js +0 -152
  767. package/node_modules/ws/lib/websocket-server.js +0 -562
  768. package/node_modules/ws/lib/websocket.js +0 -1407
  769. package/node_modules/ws/package.json +0 -74
  770. package/node_modules/ws/wrapper.mjs +0 -21
  771. package/node_modules/y18n/CHANGELOG.md +0 -35
  772. package/node_modules/y18n/LICENSE +0 -13
  773. package/node_modules/y18n/README.md +0 -109
  774. package/node_modules/y18n/index.js +0 -188
  775. package/node_modules/y18n/package.json +0 -39
  776. package/node_modules/yargs/CHANGELOG.md +0 -420
  777. package/node_modules/yargs/LICENSE +0 -21
  778. package/node_modules/yargs/README.md +0 -140
  779. package/node_modules/yargs/build/lib/apply-extends.d.ts +0 -2
  780. package/node_modules/yargs/build/lib/apply-extends.js +0 -65
  781. package/node_modules/yargs/build/lib/argsert.d.ts +0 -2
  782. package/node_modules/yargs/build/lib/argsert.js +0 -65
  783. package/node_modules/yargs/build/lib/command.d.ts +0 -64
  784. package/node_modules/yargs/build/lib/command.js +0 -416
  785. package/node_modules/yargs/build/lib/common-types.d.ts +0 -36
  786. package/node_modules/yargs/build/lib/common-types.js +0 -25
  787. package/node_modules/yargs/build/lib/completion-templates.d.ts +0 -2
  788. package/node_modules/yargs/build/lib/completion-templates.js +0 -50
  789. package/node_modules/yargs/build/lib/completion.d.ts +0 -21
  790. package/node_modules/yargs/build/lib/completion.js +0 -135
  791. package/node_modules/yargs/build/lib/is-promise.d.ts +0 -1
  792. package/node_modules/yargs/build/lib/is-promise.js +0 -9
  793. package/node_modules/yargs/build/lib/levenshtein.d.ts +0 -1
  794. package/node_modules/yargs/build/lib/levenshtein.js +0 -58
  795. package/node_modules/yargs/build/lib/middleware.d.ts +0 -10
  796. package/node_modules/yargs/build/lib/middleware.js +0 -57
  797. package/node_modules/yargs/build/lib/obj-filter.d.ts +0 -1
  798. package/node_modules/yargs/build/lib/obj-filter.js +0 -14
  799. package/node_modules/yargs/build/lib/parse-command.d.ts +0 -11
  800. package/node_modules/yargs/build/lib/parse-command.js +0 -36
  801. package/node_modules/yargs/build/lib/process-argv.d.ts +0 -2
  802. package/node_modules/yargs/build/lib/process-argv.js +0 -31
  803. package/node_modules/yargs/build/lib/usage.d.ts +0 -49
  804. package/node_modules/yargs/build/lib/usage.js +0 -540
  805. package/node_modules/yargs/build/lib/validation.d.ts +0 -34
  806. package/node_modules/yargs/build/lib/validation.js +0 -330
  807. package/node_modules/yargs/build/lib/yargs.d.ts +0 -274
  808. package/node_modules/yargs/build/lib/yargs.js +0 -1190
  809. package/node_modules/yargs/build/lib/yerror.d.ts +0 -4
  810. package/node_modules/yargs/build/lib/yerror.js +0 -11
  811. package/node_modules/yargs/index.js +0 -40
  812. package/node_modules/yargs/locales/be.json +0 -46
  813. package/node_modules/yargs/locales/de.json +0 -46
  814. package/node_modules/yargs/locales/en.json +0 -51
  815. package/node_modules/yargs/locales/es.json +0 -46
  816. package/node_modules/yargs/locales/fi.json +0 -49
  817. package/node_modules/yargs/locales/fr.json +0 -53
  818. package/node_modules/yargs/locales/hi.json +0 -49
  819. package/node_modules/yargs/locales/hu.json +0 -46
  820. package/node_modules/yargs/locales/id.json +0 -50
  821. package/node_modules/yargs/locales/it.json +0 -46
  822. package/node_modules/yargs/locales/ja.json +0 -51
  823. package/node_modules/yargs/locales/ko.json +0 -49
  824. package/node_modules/yargs/locales/nb.json +0 -44
  825. package/node_modules/yargs/locales/nl.json +0 -49
  826. package/node_modules/yargs/locales/nn.json +0 -44
  827. package/node_modules/yargs/locales/pirate.json +0 -13
  828. package/node_modules/yargs/locales/pl.json +0 -49
  829. package/node_modules/yargs/locales/pt.json +0 -45
  830. package/node_modules/yargs/locales/pt_BR.json +0 -48
  831. package/node_modules/yargs/locales/ru.json +0 -46
  832. package/node_modules/yargs/locales/th.json +0 -46
  833. package/node_modules/yargs/locales/tr.json +0 -48
  834. package/node_modules/yargs/locales/zh_CN.json +0 -48
  835. package/node_modules/yargs/locales/zh_TW.json +0 -47
  836. package/node_modules/yargs/package.json +0 -92
  837. package/node_modules/yargs/yargs.js +0 -14
  838. package/node_modules/yargs-parser/CHANGELOG.md +0 -601
  839. package/node_modules/yargs-parser/LICENSE.txt +0 -14
  840. package/node_modules/yargs-parser/README.md +0 -449
  841. package/node_modules/yargs-parser/index.js +0 -1032
  842. package/node_modules/yargs-parser/lib/tokenize-arg-string.js +0 -40
  843. package/node_modules/yargs-parser/package.json +0 -46
package/dist/web/chat.js DELETED
@@ -1,3830 +0,0 @@
1
- const params = new URLSearchParams(location.search);
2
- const queryToken = params.get('token');
3
- const debugReloadSnapshotKey = 'codexRelaxDebugReloadSnapshot';
4
- let localReloadSnapshot = '';
5
- try {
6
- localReloadSnapshot = sessionStorage.getItem(debugReloadSnapshotKey) || '';
7
- sessionStorage.removeItem(debugReloadSnapshotKey);
8
- } catch {}
9
- let localReloadState;
10
- try { localReloadState = localReloadSnapshot ? JSON.parse(localReloadSnapshot) : null; } catch {}
11
- const platform = window.createClientPlatform();
12
- const connectionConfig = window.codexLanConfig || window.codexSelfRelayConfig;
13
- const waitForInitialThreads = connectionConfig?.waitForInitialThreads === true;
14
- const localReloadThreadId = localReloadState?.timeline?.thread?.id || '';
15
- const resumeConfig = localReloadState ? { ...connectionConfig, resumeConnection: true,
16
- resumeThreadId: localReloadThreadId, resumeSnapshot: localReloadSnapshot } : connectionConfig;
17
- const pageResume = resumeConfig?.resumeConnection === true;
18
- if (!pageResume || waitForInitialThreads) document.body.classList.add('connection-entry');
19
- if (platform.embedded) {
20
- document.body.classList.add('embedded-terminal');
21
- if (!pageResume || !resumeConfig.resumeThreadId || localReloadState?.sidebarOpen) document.body.classList.add('sidebar-open');
22
- }
23
- document.body.classList.remove('page-loading');
24
- if (queryToken) {
25
- sessionStorage.setItem('codexRemoteToken', queryToken);
26
- history.replaceState(null, '', location.pathname + platform.query);
27
- }
28
- const token = window.codexLanConfig?.token || sessionStorage.getItem('codexRemoteToken') || '';
29
- const transport = window.createChatTransport({ token, currentThread: () => state.selectedThread || (isNewThread() ? state.newThreadProject : null) });
30
- const markdown = window.markdownit({
31
- html: false,
32
- linkify: true,
33
- typographer: false,
34
- breaks: false,
35
- });
36
- const defaultImageRenderer = markdown.renderer.rules.image;
37
- const defaultLinkCloseRenderer = markdown.renderer.rules.link_close;
38
- function localFileReference(source) {
39
- let reference = source;
40
- try { reference = decodeURI(reference); } catch { return null; }
41
- const position = reference.match(/:(\d+)(?::(\d+))?$/);
42
- const path = position ? reference.slice(0, position.index) : reference;
43
- if (!/^(?:[a-z]:[\\/]|\/(?!\/))/i.test(path)) return null;
44
- return {
45
- path,
46
- line: position ? Number(position[1]) : null,
47
- column: position?.[2] ? Number(position[2]) : null,
48
- };
49
- }
50
- function isCodeFileReference(reference) {
51
- const name = reference.path.split(/[\\/]/).filter(Boolean).at(-1)?.toLowerCase() || '';
52
- return /^(?:dockerfile|makefile|cmakelists\.txt|meson\.build|build\.gradle|podfile|gemfile)$/i.test(name)
53
- || /\.(?:c|cc|cpp|cxx|h|hh|hpp|hxx|cs|java|kt|kts|swift|m|mm|go|rs|py|pyw|rb|php|pl|lua|r|dart|js|jsx|mjs|cjs|ts|tsx|ets|vue|svelte|html?|css|scss|sass|less|xml|xaml|json|jsonc|ya?ml|toml|ini|cfg|conf|properties|gradle|sql|graphql|gql|sh|bash|zsh|fish|ps1|psm1|psd1|bat|cmd|md|markdown)$/i.test(name);
54
- }
55
- function relayImagePath(source) {
56
- if (!transport.has('remoteImages')) return null;
57
- let path = source;
58
- try { path = decodeURI(path); } catch { return null; }
59
- if (!/^(?:[a-z]:[\\/]|\/(?!\/))/i.test(path) || !/\.(?:png|jpe?g|webp|gif)$/i.test(path)) return null;
60
- return path;
61
- }
62
- function relayFilePath(source) {
63
- if (!transport.has('remoteFiles')) return null;
64
- return localFileReference(source)?.path || null;
65
- }
66
- markdown.renderer.rules.image = (tokens, index, options, env, renderer) => {
67
- const item = tokens[index];
68
- const source = item.attrGet('src');
69
- const path = relayImagePath(source);
70
- if (path) {
71
- env.imageAssets[source] ||= { remotePath: path };
72
- return `<span data-relay-image="${markdown.utils.escapeHtml(source)}" data-image-alt="${markdown.utils.escapeHtml(item.content || '图片')}"></span>`;
73
- }
74
- const asset = env.imageAssets?.[source];
75
- if (asset?.remotePath) {
76
- return `<span data-relay-image="${markdown.utils.escapeHtml(source)}" data-image-alt="${markdown.utils.escapeHtml(item.content || '图片')}"></span>`;
77
- }
78
- if (asset) {
79
- item.attrSet('src', authorizedAssetUrl(asset.thumbnailUrl));
80
- item.attrSet('data-full-image', authorizedAssetUrl(asset.url));
81
- item.attrSet('loading', 'lazy');
82
- item.attrSet('tabindex', '0');
83
- item.attrSet('role', 'button');
84
- }
85
- return defaultImageRenderer(tokens, index, options, env, renderer);
86
- };
87
- markdown.renderer.rules.link_open = (tokens, index, options, env, renderer) => {
88
- const item = tokens[index];
89
- const source = item.attrGet('href');
90
- const localFile = localFileReference(source);
91
- const file = env.fileAssets?.[source];
92
- const path = env.allowRemoteFiles ? relayFilePath(source) : null;
93
- const asset = env.imageAssets?.[source];
94
- if (localFile && (localFile.line || isCodeFileReference(localFile))) {
95
- item.attrSet('href', '#');
96
- item.attrSet('data-code-reference', 'true');
97
- item.attrSet('aria-disabled', 'true');
98
- } else if (asset?.remotePath) {
99
- item.attrSet('href', '#');
100
- item.attrSet('data-download-image', asset.remotePath);
101
- item.attrSet('data-download-recovery', asset.recoveryToken || '');
102
- item.attrSet('data-download-name', asset.name || (source.split(/[\\/]/).filter(Boolean).at(-1) || '图片'));
103
- } else if (file?.remotePath || path) {
104
- item.attrSet('href', '#');
105
- item.attrSet('data-download-path', file?.remotePath || path);
106
- item.attrSet('data-download-name', file?.name || (path?.split(/[\\/]/).filter(Boolean).at(-1) || '下载文件'));
107
- } else if (asset) {
108
- item.attrSet('href', authorizedAssetUrl(asset.url));
109
- item.attrSet('data-full-image', authorizedAssetUrl(asset.url));
110
- } else {
111
- if (/^https?:\/\//i.test(source)) {
112
- item.attrSet('data-external-link', source);
113
- }
114
- }
115
- if (localFile?.line) {
116
- let depth = 1;
117
- for (let next = index + 1; next < tokens.length; next += 1) {
118
- if (tokens[next].type === 'link_open') depth += 1;
119
- if (tokens[next].type !== 'link_close' || --depth !== 0) continue;
120
- tokens[next].meta ||= {};
121
- tokens[next].meta.localFilePosition = localFile.column
122
- ? ` (line ${localFile.line}, column ${localFile.column})`
123
- : ` (line ${localFile.line})`;
124
- break;
125
- }
126
- }
127
- return renderer.renderToken(tokens, index, options);
128
- };
129
- markdown.renderer.rules.link_close = (tokens, index, options, env, renderer) => {
130
- const position = tokens[index].meta?.localFilePosition || '';
131
- const closing = defaultLinkCloseRenderer
132
- ? defaultLinkCloseRenderer(tokens, index, options, env, renderer)
133
- : renderer.renderToken(tokens, index, options);
134
- return position ? `${markdown.utils.escapeHtml(position)}${closing}` : closing;
135
- };
136
- const codeLanguageLabels = {
137
- arduino: 'Arduino', ino: 'Arduino', bash: 'Bash', sh: 'Bash', zsh: 'Zsh',
138
- c: 'C', h: 'C', cpp: 'C++', 'c++': 'C++', cc: 'C++', cxx: 'C++', hpp: 'C++',
139
- csharp: 'C#', 'c#': 'C#', cs: 'C#', css: 'CSS', diff: 'Diff', patch: 'Diff',
140
- go: 'Go', golang: 'Go', graphql: 'GraphQL', gql: 'GraphQL', ini: 'INI', toml: 'TOML',
141
- java: 'Java', jsp: 'JSP', javascript: 'JavaScript', js: 'JavaScript', jsx: 'JavaScript',
142
- mjs: 'JavaScript', cjs: 'JavaScript', json: 'JSON', jsonc: 'JSON', kotlin: 'Kotlin', kt: 'Kotlin',
143
- latex: 'LaTeX', tex: 'TeX', less: 'Less', lua: 'Lua', makefile: 'Makefile', make: 'Makefile',
144
- markdown: 'Markdown', md: 'Markdown', matlab: 'MATLAB', mermaid: 'Mermaid', nginx: 'Nginx',
145
- objectivec: 'Objective-C', objc: 'Objective-C', perl: 'Perl', php: 'PHP',
146
- plaintext: '纯文本', text: '纯文本', txt: '纯文本', powershell: 'PowerShell', ps: 'PowerShell',
147
- ps1: 'PowerShell', pwsh: 'PowerShell', python: 'Python', py: 'Python', 'python-repl': 'Python REPL',
148
- pycon: 'Python REPL', r: 'R', ruby: 'Ruby', rb: 'Ruby', rust: 'Rust', rs: 'Rust',
149
- scss: 'SCSS', shell: 'Shell', console: 'Shell', shellsession: 'Shell', sql: 'SQL', swift: 'Swift',
150
- typescript: 'TypeScript', ts: 'TypeScript', tsx: 'TypeScript', cts: 'TypeScript', mts: 'TypeScript',
151
- vbnet: 'VB.NET', vb: 'VB', wasm: 'WebAssembly', xml: 'XML', html: 'HTML', xhtml: 'XHTML',
152
- svg: 'SVG', yaml: 'YAML', yml: 'YAML',
153
- };
154
- function codeLanguageLabel(code) {
155
- const languageClass = [...code.classList].find(value => value.startsWith('language-'));
156
- const language = languageClass?.slice('language-'.length).trim();
157
- return language ? (codeLanguageLabels[language.toLowerCase()] || language) : '代码';
158
- }
159
- function highlightCode(code, value) {
160
- const languageClass = [...code.classList].find(item => item.startsWith('language-'));
161
- const language = languageClass?.slice('language-'.length).trim().toLowerCase();
162
- if (!language || value.length > 65536 || !window.hljs?.getLanguage(language)) return;
163
- try {
164
- code.innerHTML = window.hljs.highlight(value, { language, ignoreIllegals: true }).value;
165
- code.classList.add('hljs');
166
- } catch {}
167
- }
168
- function hasOpenCodeFence(text) {
169
- let openFence = null;
170
- for (const line of String(text || '').split(/\r?\n/)) {
171
- const opening = line.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
172
- if (!opening) continue;
173
- const marker = opening[1];
174
- if (!openFence) {
175
- openFence = { character: marker[0], length: marker.length };
176
- continue;
177
- }
178
- if (marker[0] === openFence.character && marker.length >= openFence.length && !opening[2].trim()) openFence = null;
179
- }
180
- return Boolean(openFence);
181
- }
182
- function codeBlockIcon(kind) {
183
- const namespace = 'http://www.w3.org/2000/svg';
184
- const svg = document.createElementNS(namespace, 'svg');
185
- svg.classList.add('code-block-copy-icon');
186
- svg.setAttribute('viewBox', '0 0 24 24');
187
- svg.setAttribute('fill', 'none');
188
- svg.setAttribute('stroke', 'currentColor');
189
- svg.setAttribute('stroke-width', '1.8');
190
- svg.setAttribute('stroke-linecap', 'round');
191
- svg.setAttribute('stroke-linejoin', 'round');
192
- svg.setAttribute('aria-hidden', 'true');
193
- const paths = kind === 'check'
194
- ? [['path', { d: 'M20 6 9 17l-5-5' }]]
195
- : [['rect', { x: '8', y: '8', width: '14', height: '14', rx: '2', ry: '2' }],
196
- ['path', { d: 'M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2' }]];
197
- for (const [tag, attributes] of paths) {
198
- const node = document.createElementNS(namespace, tag);
199
- for (const [name, value] of Object.entries(attributes)) node.setAttribute(name, value);
200
- svg.append(node);
201
- }
202
- return svg;
203
- }
204
- function legacyCopyText(value) {
205
- const input = document.createElement('textarea');
206
- input.value = value;
207
- input.setAttribute('readonly', '');
208
- input.style.position = 'fixed';
209
- input.style.inset = '-9999px auto auto -9999px';
210
- document.body.append(input);
211
- input.select();
212
- input.setSelectionRange(0, input.value.length);
213
- let copied = false;
214
- try { copied = document.execCommand('copy'); } catch {}
215
- input.remove();
216
- return copied;
217
- }
218
- async function copyCodeText(value) {
219
- if (navigator.clipboard?.writeText && window.isSecureContext) {
220
- try { await navigator.clipboard.writeText(value); return; } catch {}
221
- }
222
- if (legacyCopyText(value)) return;
223
- if (window.codexNative?.copyText) { window.codexNative.copyText(value); return; }
224
- throw new Error('Clipboard unavailable');
225
- }
226
- function enhanceCodeBlocks(content, source) {
227
- const blocks = [...content.querySelectorAll('pre > code')];
228
- const unfinishedBlock = hasOpenCodeFence(source) ? blocks.at(-1) : null;
229
- for (const code of blocks) {
230
- const pre = code.parentElement;
231
- const codeText = code.textContent || '';
232
- const wrapper = document.createElement('div');
233
- wrapper.className = 'code-block';
234
- const header = document.createElement('div');
235
- header.className = 'code-block-header';
236
- const language = document.createElement('span');
237
- language.className = 'code-block-language';
238
- language.textContent = codeLanguageLabel(code);
239
- header.append(language);
240
- if (code !== unfinishedBlock) {
241
- highlightCode(code, codeText);
242
- const copy = document.createElement('button');
243
- copy.type = 'button';
244
- copy.className = 'code-block-copy';
245
- copy.title = '复制代码';
246
- copy.setAttribute('aria-label', '复制代码');
247
- copy.append(codeBlockIcon('copy'));
248
- copy.onclick = async event => {
249
- event.stopPropagation();
250
- if (copy.dataset.copyState === 'copied') return;
251
- try {
252
- await copyCodeText(codeText);
253
- copy.dataset.copyState = 'copied';
254
- copy.title = '已复制';
255
- copy.setAttribute('aria-label', '已复制');
256
- copy.replaceChildren(codeBlockIcon('check'));
257
- setTimeout(() => {
258
- if (!copy.isConnected) return;
259
- delete copy.dataset.copyState;
260
- copy.title = '复制代码';
261
- copy.setAttribute('aria-label', '复制代码');
262
- copy.replaceChildren(codeBlockIcon('copy'));
263
- }, 2000);
264
- } catch {
265
- showActionToast('复制失败,可长按代码块选择文字。', 3000);
266
- }
267
- };
268
- header.append(copy);
269
- }
270
- pre.replaceWith(wrapper);
271
- wrapper.append(header, pre);
272
- }
273
- }
274
- const state = {
275
- draftImages: [],
276
- uploadingImages: false,
277
- sending: false,
278
- selectedId: null,
279
- selectedThread: null,
280
- threads: [],
281
- threadListComplete: false,
282
- archivedThreads: [],
283
- showingArchived: false,
284
- projects: [],
285
- collapsedProjects: new Set(),
286
- unreadThreads: new Set(),
287
- threadSnapshots: new Map(),
288
- newThreadProject: null,
289
- timeline: null,
290
- eventSource: null,
291
- connection: 'connecting',
292
- currentModel: '',
293
- currentThinking: '',
294
- currentServiceTier: null,
295
- currentPermissionMode: null,
296
- permissionModes: [],
297
- menuThinking: '',
298
- pendingSettingsByThread: new Map(),
299
- timelineAtBottom: true,
300
- selectionVersion: 0,
301
- historyCache: { read: 'idle', write: 'idle', threadId: '', readMs: 0, writeMs: 0,
302
- turns: 0, retained: true, evicted: 0, error: '', changedAt: 0 },
303
- };
304
- const conversation = window.createConversationController(state);
305
- const history = window.createHistoryController({ state, transport,
306
- api: (path, options) => api(path, options), isThreadRunning,
307
- report: detail => reportSyncMetric(detail), onCacheError: error => cacheError(error) });
308
- const disclosures = window.createDisclosureStateController({ getThreadId: () => state.selectedId });
309
- const pendingMessages = window.createPendingMessageStore();
310
- const { activityPreview, commandStatusText, diffLineCounts, formatDuration, formatTime,
311
- isVisibleProcessActivity, liveStepText, operationIconKind, operationSummary,
312
- processDurationMs, reasoningSummaryTexts, statusText, timeMilliseconds,
313
- toolStatusText, turnErrorMessage, turnLabel, webSearchDetail, webSearchTitle } = window.CodexTimelineFormatters;
314
- const { parseQuestionReply } = window.CodexMessageView;
315
- let messageView;
316
- let timelineRenderer;
317
- let timelineScroll;
318
- const queue = window.createQueueController({
319
- document, el: id => document.getElementById(id), api: (path, options) => api(path, options), state, pendingMessages,
320
- isComposerActive: composerIsActive, isConnectionBlocked: connectionBlocked, isThreadReadOnly: threadReadOnly,
321
- renderPendingMessages, scrollTimelineToBottom, showToast: showActionToast,
322
- });
323
- const threadContext = window.createThreadContextController({
324
- load: threadId => api(`/api/threads/${encodeURIComponent(threadId)}/context`),
325
- isCurrent: threadId => state.selectedId === threadId,
326
- onUsage: renderContextUsage,
327
- onUnavailable: () => renderContextUsage({ available: false }),
328
- });
329
- const approvalFlagOverrides = new Map();
330
- const archivedThreadsEntryVisible = false;
331
- const el = id => document.getElementById(id);
332
- let communityView;
333
- timelineScroll = window.createTimelineScrollController({
334
- document,
335
- state,
336
- getTimeline: () => el('timeline'),
337
- getSelected: () => el('selected'),
338
- getScrollButton: () => el('scrollToBottom'),
339
- });
340
- const linkActions = window.createLinkActionController({
341
- document,
342
- transport,
343
- getSelectedThreadId: () => state.selectedId,
344
- showToast: showActionToast,
345
- });
346
- const activityView = window.createActivityView({
347
- document,
348
- disclosures,
349
- entryDisclosureKey,
350
- activityIcon,
351
- disclosureChevronElement,
352
- codeBlockIcon,
353
- copyText: copyCodeText,
354
- showToast: showActionToast,
355
- compactActivityElement,
356
- activityPreview,
357
- commandStatusText,
358
- diffLineCounts,
359
- formatDuration,
360
- statusText,
361
- toolStatusText,
362
- });
363
- const approvalPanel = createApprovalPanel(el('approvalPanel'), (request, result) => {
364
- const thread = { id: state.selectedId, hostId: requestHostId };
365
- return transport.respondApproval(thread, request, result);
366
- }, setThreadApprovalPending);
367
- const composer = window.createComposerController({
368
- state, transport, platform, el, pendingMessages,
369
- actionState, isConnectionBlocked: connectionBlocked, isThreadReadOnly: threadReadOnly,
370
- isStopping: threadId => stoppingThreads.has(threadId),
371
- setApprovalConnected: connected => approvalPanel.setConnected(connected),
372
- renderFastMode, renderPermissionControl, renderSharedServiceNotice,
373
- });
374
- let requestHostId;
375
- const threadAttentionScope = transport.draftScope();
376
- const attention = window.createThreadAttentionController({
377
- state, platform, localStorage, scope: threadAttentionScope,
378
- isThreadRunning,
379
- isSelectedVisible: () => Boolean(state.selectedId && state.timeline?.thread?.id === state.selectedId
380
- && !state.historyLoading && document.visibilityState === 'visible'
381
- && !document.body.classList.contains('sidebar-open')),
382
- report: detail => reportSyncMetric(detail),
383
- });
384
- let enteredConversation = false;
385
- let entryWork, entryRetryTimer, entryAttempts = 0;
386
- const terminalConnectionCodes = new Set([1008, 4401, 4406, 4408, 4409, 4428, 4429]);
387
- const isTerminalConnectionError = error => terminalConnectionCodes.has(error?.code);
388
- const stationConnection = window.createStationConnectionController({
389
- connect: () => transport.has('managedConnection') ? transport.resume() : api('/api/health'),
390
- restore: restoreRelayConversation,
391
- isConnected: () => transport.connectionState() === 'connected',
392
- isTerminal: isTerminalConnectionError,
393
- onSuspend: () => { clearTimeout(entryRetryTimer); entryRetryTimer = undefined; },
394
- onStarted: () => reportSyncMetric(`stage=connection;action=recover;state=started;managed=${transport.has('managedConnection') ? 1 : 0}`),
395
- onAttempt: () => {
396
- setConversationConnection('connecting');
397
- closeEventStream(); threadContext.stop(); stopThreadPolling();
398
- },
399
- onRecovered: attempts => {
400
- setConversationConnection('connected');
401
- usage.start(); startThreadPolling();
402
- reportSyncMetric(`stage=connection;action=recover;state=completed;attempts=${attempts}`);
403
- },
404
- onRetry: () => {
405
- state.historyLoading = false; state.historyProgress = '';
406
- setConversationConnection('connecting');
407
- },
408
- onRestoreFailure: error => {
409
- state.historyLoading = false; state.historyProgress = '';
410
- setConversationConnection('connected');
411
- reportSyncMetric(`stage=connection;action=recover;state=stopped;reason=timeline`);
412
- setStatus(`会话读取已停止:${error.message}`);
413
- },
414
- onTerminal: error => {
415
- state.historyLoading = false; state.historyProgress = '';
416
- setConversationConnection('disconnected');
417
- el('connectionNoticeText').textContent = error.code === 1008 ? '配对已失效,请重新扫码' : error.message;
418
- reportSyncMetric(`stage=connection;action=recover;state=terminal;code=${error.code}`);
419
- },
420
- });
421
- transport.onConnectionChange?.(value => {
422
- if (!enteredConversation || value !== 'disconnected') return;
423
- setConversationConnection(stationConnection.isPaused() ? 'disconnected' : 'connecting');
424
- if (!stationConnection.isPaused()) void stationConnection.recover();
425
- });
426
-
427
- window.codexComputerExecutionChanged = () => {
428
- if (!transport.has('managedConnection')) { void stationConnection.recover(); return; }
429
- void api('/api/health').then(() => {
430
- renderConnectionLabel(); renderSharedServiceNotice(); updateComposerMode();
431
- }).catch(() => {});
432
- };
433
-
434
- const api = async (path, options = {}) => {
435
- if (connectionBlocked() && options.method && options.method !== 'GET') throw new Error('连接尚未恢复');
436
- try { return await transport.api(path, options); }
437
- catch (error) {
438
- if (error?.code === 'thread_writer_owned_elsewhere' && error.threadId === state.selectedId && state.timeline?.thread) {
439
- state.timeline.thread.readOnly = true;
440
- state.timeline.thread.readOnlyReason = error.message;
441
- updateComposerMode(); renderSharedServiceNotice();
442
- }
443
- if (error instanceof TypeError) setConversationConnection(stationConnection.isRecovering() ? 'connecting' : 'disconnected');
444
- throw error;
445
- }
446
- };
447
- const usage = window.createUsageController({ document, el, transport, api });
448
-
449
- let foregroundChecking = false;
450
- function connectionBlocked() { return foregroundChecking || (state.connection && state.connection !== 'connected'); }
451
- function renderConnectionLabel() {
452
- const target = el('connectionAddress');
453
- const label = transport.connectionLabel(true);
454
- const mode = transport.transportMode?.();
455
- const separator = label.lastIndexOf(' · ');
456
- if (!['lan-shared', 'lan-legacy'].includes(mode) || separator < 0) {
457
- target.textContent = label;
458
- return;
459
- }
460
- const control = document.createElement('span');
461
- control.className = `connection-control-mode ${mode === 'lan-shared' ? 'complete' : 'basic'}`;
462
- control.textContent = label.slice(separator + 3);
463
- target.replaceChildren(document.createTextNode(label.slice(0, separator + 3)), control);
464
- }
465
- function actionState(action, busy = false) {
466
- return transport.actionState(action, { connected: !connectionBlocked(), busy,
467
- active: composerIsActive(), syncing: Boolean(state.historyLoading || state.timeline?.cached),
468
- writable: !threadReadOnly(), readOnlyReason: state.timeline?.thread?.readOnlyReason });
469
- }
470
- function threadReadOnly() { return !isNewThread() && state.timeline?.thread?.readOnly === true; }
471
- function renderSharedServiceNotice() {
472
- const connected = state.connection === 'connected';
473
- const readOnly = connected && threadReadOnly();
474
- const advisory = readOnly ? {
475
- title: '该对话正在另一个 Codex 实例中使用',
476
- detail: '当前仅可查看,内容可能尚未同步。点击重新检测,不会发送草稿。'
477
- } : connected ? transport.sharedServiceNotice?.() : null;
478
- const notice = el('sharedServiceNotice');
479
- notice.dataset.action = readOnly ? 'retry-thread' : 'recover-shared';
480
- notice.dataset.kind = readOnly ? 'thread-read-only' : advisory?.kind || '';
481
- notice.hidden = !advisory;
482
- if (advisory) {
483
- el('sharedServiceNoticeTitle').textContent = advisory.title;
484
- el('sharedServiceNoticeText').textContent = advisory.detail;
485
- notice.setAttribute('aria-label', `${advisory.title}。${advisory.detail}`);
486
- }
487
- platform.reportTransportMode?.(connected ? transport.transportMode?.() || '' : '');
488
- }
489
- function setConversationConnection(value, preserveVisibleConnection = false) {
490
- const connected = value === 'connected';
491
- const visiblyConnected = connected || preserveVisibleConnection;
492
- const syncing = connected && Boolean(state.historyLoading);
493
- renderConnectionLabel();
494
- state.connection = value;
495
- approvalPanel.setConnected(connected);
496
- el('emptyConnection').textContent = visiblyConnected ? '已连接 · ' + transport.connectionLabel() : value === 'connecting' ? '正在连接工作站' : '连接已断开';
497
- if (value !== 'connected') { stopThreadPolling(); threadContext.stop(); }
498
- if (platform.embedded) platform.reportStatus('', visiblyConnected);
499
- const notice = el('connectionNotice');
500
- notice.hidden = preserveVisibleConnection || connected && !syncing;
501
- el('dot').classList.toggle('ok', visiblyConnected);
502
- el('status').textContent = syncing ? '正在同步会话…' : visiblyConnected ? '已连接' : value === 'connecting' ? '正在连接…' : '连接已断开';
503
- notice.classList.toggle('connecting', value === 'connecting' || syncing);
504
- el('connectionNoticeText').textContent = syncing ? (state.historyProgress || '正在同步会话…') : value === 'connecting' ? '正在连接…' : '连接已断开';
505
- const reconnectConversation = el('reconnectConversation');
506
- reconnectConversation.hidden = connected || value === 'connecting';
507
- reconnectConversation.disabled = reconnectConversation.hidden;
508
- el('modelTrigger').disabled = Boolean(connectionBlocked() || threadReadOnly() || state.historyLoading);
509
- el('modelMenu').setAttribute('aria-disabled', String(Boolean(connectionBlocked() || threadReadOnly() || state.historyLoading)));
510
- renderFastMode();
511
- renderPermissionControl();
512
- el('connectionStatus').textContent = visiblyConnected ? '已连接' : value === 'connecting' ? '连接中' : '已断开';
513
- el('connectionStatus').classList.toggle('failed', !visiblyConnected);
514
- for (const button of el('queuePanel').querySelectorAll('button')) button.disabled = Boolean(connectionBlocked() || state.historyLoading);
515
- renderSharedServiceNotice();
516
- updateComposerMode();
517
- }
518
-
519
- function setStatus(text, ok = false) {
520
- renderConnectionLabel();
521
- const connected = !connectionBlocked();
522
- el('status').textContent = text;
523
- if (platform.embedded && platform.has('reportStatus')) {
524
- platform.reportStatus(connected ? text : '', connected);
525
- if (!ok && connected) platform.callbacks.showFeedback?.(text);
526
- document.body.classList.add('native-status');
527
- }
528
- }
529
-
530
- let actionToastTimer;
531
- function showActionToast(text, duration = 2000) {
532
- if (!text || /^(?:已连接|连接成功|已重新连接|连接已恢复)[。!!]?$/u.test(text.trim())) return;
533
- if (!el('actionToast').hidden && el('actionToast').textContent === text) return;
534
- clearTimeout(actionToastTimer);
535
- el('actionToast').textContent = text;
536
- el('actionToast').hidden = false;
537
- actionToastTimer = setTimeout(() => { el('actionToast').hidden = true; }, Math.min(duration, 3000));
538
- }
539
- platform.callbacks.showFeedback = text => showActionToast(text);
540
-
541
- const archiveBusy = new Set();
542
- function archiveSupported(thread) {
543
- return transport.supports?.('archive', thread) ?? transport.has('archive');
544
- }
545
-
546
- function clearArchivedSelection(threadId) {
547
- if (state.selectedId !== threadId) return;
548
- closeEventStream(); threadContext.stop(); closeModelMenu(); closePermissionMenu();
549
- conversation.clear({ resetSettings: true });
550
- el('prompt').value = ''; state.draftImages = []; renderDraftImages(); queue.render([]);
551
- el('selected').hidden = true; el('empty').hidden = false;
552
- setConversationTitle('选择聊天');
553
- }
554
-
555
- async function changeThreadArchived(thread, archived) {
556
- if (archiveBusy.has(thread.id)) return;
557
- if (state.selectedId === thread.id && threadReadOnly()) { showActionToast('该对话正在另一个 Codex 实例中使用,当前只能查看'); return; }
558
- if (!archiveSupported(thread)) { showActionToast('当前连接不支持归档,请启用完整控制'); return; }
559
- if (!archived && isThreadRunning(thread)) { showActionToast('请先停止当前任务'); return; }
560
- if (!archived && state.selectedId === thread.id && (el('prompt').value.trim() || state.draftImages.length)) {
561
- showActionToast('输入框有未发送内容,请先发送或清空'); return;
562
- }
563
- archiveBusy.add(thread.id);
564
- taskListView.setArchivePending(thread.id, archived ? '恢复中' : '归档中');
565
- let succeeded = false;
566
- try {
567
- const action = archived ? 'unarchive' : 'archive';
568
- const query = thread.hostId ? `?hostId=${encodeURIComponent(thread.hostId)}` : '';
569
- await api(`/api/threads/${encodeURIComponent(thread.id)}/${action}${query}`, { method: 'POST' });
570
- if (archived) {
571
- state.archivedThreads = state.archivedThreads.filter(item => item.id !== thread.id);
572
- state.threads = [thread, ...state.threads.filter(item => item.id !== thread.id)];
573
- } else {
574
- state.threads = state.threads.filter(item => item.id !== thread.id);
575
- state.archivedThreads = [thread, ...state.archivedThreads.filter(item => item.id !== thread.id)];
576
- attention.remove(thread.id);
577
- clearArchivedSelection(thread.id);
578
- }
579
- succeeded = true;
580
- taskListView.removeThread(thread.id);
581
- } catch (error) {
582
- showActionToast(`${archived ? '恢复' : '归档'}失败:${error.message}`, 3000);
583
- } finally {
584
- archiveBusy.delete(thread.id);
585
- if (!succeeded) taskListView.clearArchivePending(thread.id);
586
- }
587
- }
588
-
589
- async function loadArchivedThreads() {
590
- el('archivedThreads').disabled = true;
591
- try {
592
- const data = await api('/api/threads?limit=200&archived=true');
593
- if (Array.isArray(data.projects)) state.projects = data.projects;
594
- state.archivedThreads = listedThreads(data).map(thread => ({ ...thread, title: resolvedThreadTitle(thread) }));
595
- return true;
596
- } catch (error) { showActionToast(`读取归档失败:${error.message}`, 3000); }
597
- finally { el('archivedThreads').disabled = false; }
598
- return false;
599
- }
600
-
601
- async function showArchivedThreads() {
602
- if (state.showingArchived) { state.showingArchived = false; renderTasks(); return; }
603
- if (await loadArchivedThreads()) { state.showingArchived = true; renderTasks(); }
604
- }
605
-
606
- async function refreshTaskList() {
607
- if (!state.showingArchived) { await loadThreads(); return; }
608
- if (await loadArchivedThreads()) renderTasks();
609
- }
610
-
611
- const taskListView = window.createTaskListView({
612
- document, el, state, archiveBusy, archivedThreadsEntryVisible,
613
- archiveSupported, isThreadRunning, isThreadWaitingForApproval,
614
- resolvedThreadTitle, statusText,
615
- onSelectThread: thread => selectThread(thread),
616
- onArchiveThread: (thread, archived) => void changeThreadArchived(thread, archived),
617
- onOpenNewThread: (project, projectId) => openNewThreadDialog(project, projectId),
618
- onFeedback: text => showActionToast(text),
619
- });
620
-
621
- function renderTasks() {
622
- taskListView.render();
623
- }
624
-
625
- function isThreadRunning(thread) {
626
- return thread?.status === 'active' || thread?.status === 'inProgress';
627
- }
628
-
629
- function isThreadWaitingForApproval(thread) {
630
- return Array.isArray(thread?.activeFlags) && thread.activeFlags.includes('waitingOnApproval');
631
- }
632
-
633
- function setThreadApprovalFlag(thread, pending) {
634
- if (!thread) return false;
635
- const flags = Array.isArray(thread.activeFlags) ? thread.activeFlags : [];
636
- const next = pending ? [...new Set([...flags, 'waitingOnApproval'])]
637
- : flags.filter(flag => flag !== 'waitingOnApproval');
638
- if (next.length === flags.length && next.every((flag, index) => flag === flags[index])) return false;
639
- thread.activeFlags = next;
640
- return true;
641
- }
642
-
643
- function setThreadApprovalPending(threadId, pending) {
644
- if (!threadId) return;
645
- approvalFlagOverrides.set(threadId, pending);
646
- let changed = false;
647
- const listed = state.threads.find(thread => thread.id === threadId);
648
- changed = setThreadApprovalFlag(listed, pending) || changed;
649
- if (state.selectedThread?.id === threadId && state.selectedThread !== listed) {
650
- changed = setThreadApprovalFlag(state.selectedThread, pending) || changed;
651
- }
652
- if (changed) renderTasks();
653
- }
654
-
655
- function openNewThreadDialog(project, projectId) {
656
- if (platform.voice.active || state.creatingThread) return;
657
- saveActiveDraft();
658
- closeEventStream(); threadContext.stop(); closeModelMenu(); closePermissionMenu();
659
- conversation.clear();
660
- state.newThreadProject = {
661
- projectId,
662
- label: project?.label || (projectId === '__unassigned__' ? '未归属项目' : projectId),
663
- path: project?.path || '',
664
- hostId: project?.hostId || '',
665
- knownThreadIds: new Set(state.threads.map(thread => thread.id)),
666
- };
667
- el('newThreadProjectName').textContent = projectId === '__unassigned__' ? '不在项目中工作' : state.newThreadProject.label;
668
- el('clearNewThreadProject').hidden = projectId === '__unassigned__';
669
- el('newThreadTitle').textContent = projectId === '__unassigned__'
670
- ? '我们要构建什么?' : `你想让我们在 ${state.newThreadProject.label} 中构建什么?`;
671
- const draft = drafts.loadNewThread(state.newThreadProject);
672
- el('prompt').value = draft.text;
673
- state.draftImages = draft.images;
674
- renderDraftImages();
675
- el('empty').hidden = true; el('selected').hidden = false;
676
- setNewThreadState(true);
677
- el('timeline').replaceChildren(); queue.render([]); el('scrollToBottom').hidden = true;
678
- renderContextUsage({ available:false, model:state.currentModel, thinking:state.currentThinking, serviceTier:state.currentServiceTier });
679
- updateComposerMode();
680
- document.body.classList.remove('sidebar-open');
681
- setConversationTitle('新对话'); renderTasks();
682
- requestAnimationFrame(() => el('prompt').focus());
683
- }
684
-
685
- const threadTitles = window.createThreadTitleController();
686
- const drafts = window.createDraftController({
687
- platform, transport, localStorage,
688
- scope: () => threadAttentionScope,
689
- onPersistenceError: () => setStatus('草稿暂时只能保留到本次页面关闭'),
690
- });
691
- const messageSender = window.createMessageSendController({
692
- state, transport, platform, crypto, el, pendingMessages, drafts, composer, queue, threadContext, conversation,
693
- api: (path, options) => api(path, options),
694
- isConnectionBlocked: connectionBlocked, isThreadReadOnly: threadReadOnly,
695
- isApplyingSettings: () => applyingServerSettings, isNewThread, createNewThread,
696
- setStatus, showToast: showActionToast, restoreConversation: restoreRelayConversation,
697
- renderDraftImages, renderPendingMessages, scrollTimelineToBottom, renderModelControl,
698
- });
699
-
700
- function resolvedThreadTitle(thread) {
701
- return threadTitles.resolve(thread);
702
- }
703
-
704
- function saveNewThreadDraft() {
705
- if (!isNewThread() || !state.newThreadProject) return;
706
- drafts.saveNewThread(state.newThreadProject, el('prompt').value, state.draftImages);
707
- }
708
- function saveConversationDraft(threadId) {
709
- if (!threadId) return;
710
- drafts.saveConversation(threadId, el('prompt').value, state.draftImages);
711
- }
712
- function saveActiveDraft() {
713
- drafts.flush(() => {
714
- if (isNewThread()) saveNewThreadDraft();
715
- else if (state.selectedId) saveConversationDraft(state.selectedId);
716
- });
717
- }
718
- function scheduleDraftSave() {
719
- drafts.scheduleSave(() => {
720
- if (isNewThread()) saveNewThreadDraft();
721
- else if (state.selectedId) saveConversationDraft(state.selectedId);
722
- });
723
- }
724
- function isNewThread() { return Boolean(state.newThreadActive); }
725
- function setNewThreadState(active) {
726
- state.newThreadActive = active;
727
- el('terminalChatTitle').textContent = active ? '新对话' : (el('selectedTitle').textContent || 'Code Relax');
728
- el('newThreadWelcome').hidden = !active;
729
- el('newThreadProjectBar').hidden = !active;
730
- el('timeline').hidden = active;
731
- document.querySelector('.selected-heading').hidden = active;
732
- }
733
- function showNewThreadPendingMessage(message) {
734
- el('newThreadWelcome').hidden = true;
735
- el('newThreadProjectBar').hidden = true;
736
- el('timeline').hidden = false;
737
- updatePendingMessageElement(message);
738
- el('timeline').replaceChildren(message.element);
739
- scrollTimelineToBottom(true);
740
- }
741
- async function createNewThread() {
742
- const project = state.newThreadProject;
743
- const prompt = el('prompt').value.trim();
744
- if (!project || (!prompt && !state.draftImages.length) || state.creatingThread || state.uploadingImages) return;
745
- if (connectionBlocked() || threadReadOnly()) return;
746
- state.creatingThread = true; state.sending = true;
747
- el('clearNewThreadProject').disabled = true; updatePromptActions();
748
- const draftKey = drafts.newThreadKey(state.newThreadProject);
749
- saveNewThreadDraft();
750
- const body = messageBody(prompt);
751
- const draftImages = [...state.draftImages];
752
- const initialMessage = {
753
- clientUserMessageId: body.clientUserMessageId,
754
- request: { ...body },
755
- text: prompt,
756
- attachments: draftImages.map(image => ({ ...image, type:'image', available:true, url:image.url || image.thumbnailUrl })),
757
- baseline: new Set(),
758
- settled: false,
759
- status: '正在创建对话…',
760
- };
761
- el('prompt').value = '';
762
- state.draftImages = [];
763
- renderDraftImages();
764
- showNewThreadPendingMessage(initialMessage);
765
- try {
766
- const result = await api(`/api/projects/${encodeURIComponent(project.projectId)}/threads`, {
767
- method:'POST', body:JSON.stringify(body)
768
- });
769
- initialMessage.turnId = result.turnId || result.turn?.id || null;
770
- initialMessage.settled = true;
771
- initialMessage.status = '正在打开对话…';
772
- updatePendingMessageElement(initialMessage);
773
- drafts.clearNewThread(draftKey);
774
- const created = await locateCreatedThread(result, project, prompt);
775
- if (created) {
776
- if (isNewThread() && state.newThreadProject === project) {
777
- initialMessage.status = '';
778
- updatePendingMessageElement(initialMessage);
779
- await selectThread(created, initialMessage);
780
- }
781
- scheduleThreadRefresh(1_500, false, 'thread-created');
782
- } else {
783
- initialMessage.status = '新任务已提交,仍在准备;稍后刷新任务列表即可打开。';
784
- updatePendingMessageElement(initialMessage);
785
- setStatus(initialMessage.status, true);
786
- }
787
- } catch (error) {
788
- initialMessage.settled = true;
789
- initialMessage.status = `创建失败:${error.message}`;
790
- updatePendingMessageElement(initialMessage);
791
- if (state.newThreadProject === project && isNewThread()) {
792
- el('prompt').value = prompt;
793
- state.draftImages = draftImages;
794
- renderDraftImages();
795
- }
796
- setStatus(initialMessage.status);
797
- }
798
- finally { state.creatingThread = false; state.sending = false; el('clearNewThreadProject').disabled = false; updatePromptActions(); }
799
- }
800
-
801
- async function locateCreatedThread(result, project, prompt) {
802
- if (result?.threadId) {
803
- threadTitles.remember(result.threadId, prompt);
804
- await loadThreads();
805
- let created = state.threads.find(thread => thread.id === result.threadId);
806
- if (!created) {
807
- created = {
808
- id: result.threadId,
809
- title: resolvedThreadTitle({ id: result.threadId }),
810
- status: 'active',
811
- projectId: project.projectId === '__unassigned__' ? null : project.projectId,
812
- hostId: result.hostId || project.hostId,
813
- cwd: project.path,
814
- };
815
- state.threads.unshift(created);
816
- renderTasks();
817
- }
818
- return created;
819
- }
820
- if (!result?.clientThreadId) return null;
821
- for (let attempt = 0; attempt < 8; attempt += 1) {
822
- if (attempt) await new Promise(resolve => setTimeout(resolve, 1_250));
823
- await loadThreads();
824
- const created = state.threads.find(thread => !project.knownThreadIds.has(thread.id)
825
- && (project.projectId === '__unassigned__' ? !thread.projectId : thread.projectId === project.projectId));
826
- if (created) {
827
- threadTitles.remember(created.id, prompt);
828
- created.title = resolvedThreadTitle(created);
829
- renderTasks();
830
- return created;
831
- }
832
- }
833
- return null;
834
- }
835
-
836
- const threadList = window.createThreadListController({
837
- state, approvalOverrides: approvalFlagOverrides,
838
- resolveTitle: thread => resolvedThreadTitle(thread),
839
- attention,
840
- isWaitingForApproval: isThreadWaitingForApproval,
841
- setApprovalFlag: setThreadApprovalFlag,
842
- onSelected: thread => setConversationTitle(thread.title || thread.id),
843
- onRender: renderTasks,
844
- });
845
-
846
- function listedThreads(threadData) {
847
- return threadList.listed(threadData);
848
- }
849
-
850
- function applyThreadData(threadData, forceRender = false, source = 'list') {
851
- threadList.apply(threadData, forceRender, source);
852
- }
853
-
854
- const threadListSync = window.createThreadListSync({
855
- api, isBlocked: connectionBlocked, isVisible: taskListVisible,
856
- apply: applyThreadData, count: data => listedThreads(data).length,
857
- setStatus, setRefreshDisabled: disabled => { el('refresh').disabled = disabled; },
858
- report: detail => reportSyncMetric(detail),
859
- });
860
-
861
- function refreshThreadStates(reason = 'refresh') {
862
- return threadListSync.refresh(reason);
863
- }
864
-
865
- async function refreshCurrentConversation() {
866
- if (state.historyLoading) return;
867
- if (connectionBlocked()) { showActionToast('连接已断开,请先重新连接'); return; }
868
- if (!state.selectedThread) return;
869
- try {
870
- await restoreRelayConversation();
871
- } catch (error) { setStatus(error.message, false); }
872
- }
873
- window.harmonyRefreshConversation = refreshCurrentConversation;
874
-
875
- function pageSnapshot() {
876
- return JSON.stringify({ selectedId: state.selectedId, selectedThread: state.selectedThread,
877
- timeline: state.timeline ? cacheableTimeline({ ...state.timeline, thread: state.selectedThread,
878
- turns: state.timeline.turns.slice(-40) }) : null,
879
- projects: state.projects, threads: state.threads, threadListComplete: state.threadListComplete,
880
- archivedThreads: state.showingArchived ? state.archivedThreads : [],
881
- showingArchived: state.showingArchived, collapsedProjects: [...state.collapsedProjects],
882
- disclosures: disclosures.entries(), sidebarOpen: document.body.classList.contains('sidebar-open'),
883
- taskScrollTop: (document.body.classList.contains('embedded-terminal') ? el('tasks') : document.querySelector('aside'))?.scrollTop || 0,
884
- newThreadProject: isNewThread() ? state.newThreadProject : null,
885
- draft: el('prompt').value, draftImages: state.draftImages,
886
- scrollTop: el('timeline').scrollTop, atBottom: state.timelineAtBottom });
887
- }
888
- window.harmonyCapturePageSnapshot = pageSnapshot;
889
-
890
- function openLatestDebugPage() {
891
- if (platform.has('debugAccessState') && !platform.debugAccessState()) {
892
- showActionToast('当前设备未获调试页面权限');
893
- return;
894
- }
895
- const threadId = state.selectedId || '', snapshot = pageSnapshot();
896
- try {
897
- if (/^https?:$/.test(location.protocol)) {
898
- try {
899
- sessionStorage.setItem(debugReloadSnapshotKey, snapshot);
900
- location.reload();
901
- } catch (error) {
902
- if (!platform.has('reloadPage')) throw error;
903
- platform.reloadPage(threadId, snapshot);
904
- }
905
- } else if (!window.codexSelfRelayConfig && platform.has('setExternalUi')) {
906
- platform.setExternalUi(true, threadId, snapshot);
907
- } else {
908
- showActionToast('当前连接不支持工作站调试页面');
909
- }
910
- } catch (error) { showActionToast(error.message); }
911
- }
912
-
913
- function readLastVisitedThread() {
914
- try {
915
- const scope = transport.draftScope();
916
- const value = platform.has('readLastThread') ? platform.readLastThread(scope)
917
- : localStorage.getItem('codexRemoteLastThread:' + scope);
918
- return value ? JSON.parse(value) : null;
919
- } catch (error) {
920
- cacheError(error);
921
- return null;
922
- }
923
- }
924
-
925
- function rememberVisitedThread(thread) {
926
- if (!platform.embedded) return;
927
- try {
928
- const scope = transport.draftScope();
929
- const value = JSON.stringify({ id: thread.id, hostId: thread.hostId, title: thread.title,
930
- projectId: thread.projectId, cwd: thread.cwd, transport: thread.transport });
931
- if (platform.has('saveLastThread')) platform.saveLastThread(scope, value);
932
- else localStorage.setItem('codexRemoteLastThread:' + scope, value);
933
- } catch (error) { cacheError(error); }
934
- }
935
-
936
- async function restoreStartupView() {
937
- let saved;
938
- if (pageResume && resumeConfig.resumeSnapshot) saved = JSON.parse(resumeConfig.resumeSnapshot);
939
- const restoreTaskScroll = () => {
940
- const owner = document.body.classList.contains('embedded-terminal') ? el('tasks') : document.querySelector('aside');
941
- if (owner && Number(saved?.taskScrollTop) > 0) owner.scrollTop = Number(saved.taskScrollTop);
942
- };
943
- if (saved?.projects) state.projects = saved.projects;
944
- if (Array.isArray(saved?.threads)) state.threads = saved.threads;
945
- state.threadListComplete = saved?.threadListComplete === true;
946
- if (Array.isArray(saved?.archivedThreads)) state.archivedThreads = saved.archivedThreads;
947
- if (Array.isArray(saved?.collapsedProjects)) state.collapsedProjects = new Set(saved.collapsedProjects);
948
- disclosures.restore(saved?.disclosures);
949
- state.showingArchived = saved?.showingArchived === true;
950
- if (saved?.newThreadProject) {
951
- if (!waitForInitialThreads) {
952
- document.body.classList.remove('connection-entry'); platform.reportEntry?.(false);
953
- }
954
- openNewThreadDialog(saved.newThreadProject, saved.newThreadProject.projectId);
955
- el('prompt').value = saved.draft || ''; state.draftImages = saved.draftImages || []; renderDraftImages();
956
- restoreTaskScroll();
957
- setConversationConnection('connecting', pageResume); return;
958
- }
959
- const visited = platform.embedded ? readLastVisitedThread() : null;
960
- const cached = await window.CodexPageResume.resolveConversation({ pageResume, resumeConfig, saved,
961
- threads: state.threads, visited, readCached: (id, thread) => history.read(id, thread, 'startup'),
962
- readLastCached: () => history.readLatest('startup'), onCacheError: cacheError });
963
- if (!cached) return;
964
- conversation.restoreCached(cached, state.threads);
965
- if (!state.threads.some(thread => thread.id === cached.thread.id)) state.threads = [cached.thread];
966
- if (!waitForInitialThreads) document.body.classList.remove('connection-entry');
967
- if (!saved?.sidebarOpen) document.body.classList.remove('sidebar-open');
968
- if (!waitForInitialThreads) platform.reportEntry?.(false);
969
- el('empty').hidden = true; el('selected').hidden = false;
970
- setConversationTitle(resolvedThreadTitle(cached.thread)); renderTasks();
971
- restoreTaskScroll();
972
- try {
973
- timelineRenderer.render(state.timeline, saved?.atBottom !== false);
974
- } catch (error) {
975
- history.release();
976
- conversation.clear({ resetSettings: true });
977
- el('selected').hidden = true; el('empty').hidden = false;
978
- throw error;
979
- }
980
- if (saved) {
981
- el('prompt').value = saved.draft || ''; state.draftImages = saved.draftImages || []; renderDraftImages();
982
- if (saved.atBottom === false) el('timeline').scrollTop = saved.scrollTop || 0;
983
- }
984
- updateComposerMode(); setConversationConnection('connecting', pageResume);
985
- }
986
-
987
- function startThreadPolling() {
988
- threadListSync.start();
989
- }
990
-
991
- function taskListVisible() {
992
- if (document.hidden) return false;
993
- const rect = document.querySelector('aside').getBoundingClientRect();
994
- return rect.width > 0 && rect.right > 0 && rect.left < window.innerWidth;
995
- }
996
-
997
- function showTaskList() {
998
- const wasVisible = taskListVisible();
999
- usage.refreshSummary();
1000
- document.body.classList.remove('sidebar-collapsed');
1001
- document.body.classList.add('sidebar-open');
1002
- renderTasks();
1003
- if (!wasVisible) void refreshThreadStates('sidebar-open');
1004
- }
1005
-
1006
- function scheduleThreadRefresh(delay = 0, force = false, reason = 'scheduled') {
1007
- threadListSync.schedule(delay, force, reason);
1008
- }
1009
-
1010
- function stopThreadPolling() {
1011
- threadListSync.stop();
1012
- }
1013
-
1014
- function loadThreads(required = false) {
1015
- return threadListSync.load(required);
1016
- }
1017
-
1018
- async function selectThread(thread, initialMessage = null) {
1019
- const syncStarted = performance.now();
1020
- if (platform.embedded && platform.voice.active) return;
1021
- communityView?.close();
1022
- const selectionPlan = conversation.planSelection(thread, isNewThread());
1023
- const retained = selectionPlan.retainedTimeline;
1024
- history.cancelSave();
1025
- if (state.timeline && !state.timeline.cached && !state.historyLoading) {
1026
- void history.saveNow(cacheableTimeline(state.timeline), state.selectedThread, 'leave');
1027
- }
1028
- const switchingThread = selectionPlan.switchingThread;
1029
- if (isNewThread()) saveNewThreadDraft();
1030
- else if (switchingThread && state.selectedId) saveConversationDraft(state.selectedId);
1031
- if (switchingThread) {
1032
- const draft = drafts.loadConversation(thread.id);
1033
- el('prompt').value = draft.text;
1034
- state.draftImages = draft.images;
1035
- renderDraftImages();
1036
- }
1037
- setNewThreadState(false);
1038
- if (platform.embedded) {
1039
- document.body.classList.remove('sidebar-open');
1040
- }
1041
- rememberVisitedThread(thread);
1042
- closeEventStream();
1043
- threadContext.stop();
1044
- closeModelMenu();
1045
- closePermissionMenu();
1046
- const selection = conversation.activateThread(thread, retained);
1047
- resetModelMenuToCurrent();
1048
- syncSidebarSelection(thread);
1049
- el('empty').hidden = true;
1050
- el('selected').hidden = false;
1051
- const project = state.projects.find(item => item.projectId === thread.projectId);
1052
- setConversationTitle(resolvedThreadTitle(thread));
1053
- renderSelectedMeta(project, thread, '正在连接实时更新…', false);
1054
- if (!retained) el('timeline').replaceChildren(...(initialMessage || transport.has('syncBeforeWrite') ? [] : [messageElement('agent', '正在读取…', 'Bridge')]));
1055
- if (initialMessage) { pendingMessages.set(thread.id, [initialMessage]); renderPendingMessages(); }
1056
- if (!retained) history.release();
1057
- let cacheMilliseconds = 0;
1058
- if (!retained) {
1059
- const cacheStarted = performance.now();
1060
- const cached = await history.read(thread.id, thread, 'select');
1061
- cacheMilliseconds = Math.round(performance.now() - cacheStarted);
1062
- if (cached && conversation.isCurrent(selection, thread.id)) {
1063
- try {
1064
- state.timeline = cached; timelineRenderer.render(cached, true);
1065
- updateComposerMode();
1066
- } catch (error) {
1067
- state.timeline = null; history.release(); cacheError(error);
1068
- el('timeline').replaceChildren(messageElement('agent', '正在读取最新内容…', 'Bridge'));
1069
- }
1070
- }
1071
- if (!conversation.isCurrent(selection, thread.id)) return;
1072
- }
1073
- queue.render([]);
1074
- try {
1075
- setConversationConnection(transport.connectionState() === 'connected' ? 'connected' : 'connecting');
1076
- const fetchStarted = performance.now();
1077
- const [recovered, queueData] = await Promise.all([
1078
- history.recover(thread.id, state.timeline, () => conversation.isCurrent(selection, thread.id), thread.hostId),
1079
- api(`/api/threads/${encodeURIComponent(thread.id)}/queue`),
1080
- ]);
1081
- if (!conversation.isCurrent(selection, thread.id)) return;
1082
- const data = recovered.timeline;
1083
- const fetchMilliseconds = Math.round(performance.now() - fetchStarted);
1084
- if (!conversation.completeSync(selection, data)) return;
1085
- const renderStarted = performance.now();
1086
- timelineRenderer.render(data, true);
1087
- const renderMilliseconds = Math.round(performance.now() - renderStarted);
1088
- attention.markRead(data.thread, 'select-timeline', data.turns.at(-1)?.id);
1089
- history.releaseUnused(data, 'select');
1090
- queue.render(queueData.items || []);
1091
- updateComposerMode();
1092
- openEventStream(thread, project);
1093
- threadContext.start(thread.id);
1094
- if (!recovered.unchanged) saveHistoryCache();
1095
- reportSyncMetric(`stage=select;totalMs=${Math.round(performance.now() - syncStarted)};cacheMs=${cacheMilliseconds};fetchMs=${fetchMilliseconds};renderMs=${renderMilliseconds};${timelineShapeMetric(data)};unchanged=${recovered.unchanged ? 1 : 0};partial=${recovered.partial ? 1 : 0};listTrusted=${recovered.listTrusted ? 1 : 0}`);
1096
- } catch (error) {
1097
- if (!conversation.isCurrent(selection, thread.id)) return;
1098
- const transportConnected = transport.connectionState() === 'connected';
1099
- setConversationConnection(transportConnected ? 'connected' : 'disconnected');
1100
- conversation.failSync(selection);
1101
- showTimelineSyncFailure(thread, error.message, () => selectThread(thread));
1102
- renderSelectedMeta(project, thread, '实时更新未连接', false);
1103
- if (transportConnected) openEventStream(thread, project);
1104
- setStatus(error.message);
1105
- }
1106
- }
1107
-
1108
- let cacheWarningShown = false;
1109
- function cacheError(error) {
1110
- if (cacheWarningShown) return;
1111
- cacheWarningShown = true;
1112
- showActionToast('手机缓存不可用:' + error.message, 3000);
1113
- }
1114
- function saveHistoryCache() {
1115
- history.scheduleSave(cacheableTimeline(state.timeline), state.selectedThread, state.historyLoading);
1116
- }
1117
- function cacheableTimeline(timeline) {
1118
- if (!timeline?.turns) return timeline;
1119
- return { ...timeline, turns: timeline.turns.map(turn => ({ ...turn,
1120
- entries: (turn.entries || []).map(entry => {
1121
- if (entry?.kind !== 'lazyOperationGroup') return entry;
1122
- const { loadedEntries, detailsLoaded, ...skeleton } = entry;
1123
- return skeleton;
1124
- }),
1125
- })) };
1126
- }
1127
- function showTimelineSyncFailure(thread, message, retry) {
1128
- if (!thread || state.selectedId !== thread.id) return;
1129
- const timeline = el('timeline');
1130
- timeline.querySelector('.timeline-sync-retry')?.remove();
1131
- if (state.timeline) return;
1132
- timeline.replaceChildren(messageElement('agent', `读取失败:${message}`, 'Bridge'));
1133
- const button = document.createElement('button');
1134
- button.className = 'history-more timeline-sync-retry';
1135
- button.textContent = '重新读取';
1136
- button.onclick = async () => {
1137
- button.disabled = true; button.textContent = '正在重试…';
1138
- try { await retry(); }
1139
- catch (error) {
1140
- if (state.selectedId !== thread.id) return;
1141
- button.disabled = false; button.textContent = '重新读取'; setStatus(error.message);
1142
- }
1143
- };
1144
- timeline.append(button);
1145
- }
1146
- function historyButton(data) {
1147
- if (!transport.has('pagedHistory')) return null;
1148
- if (data.cached) return null;
1149
- if (!data.nextCursor) return null;
1150
- const gap = Boolean(data.historyGapBeforeTurnId);
1151
- const button = document.createElement('button'); button.textContent = gap ? '加载中间内容' : '加载更早的消息'; button.className = 'history-more';
1152
- const id = state.selectedId, selection = conversation.generation(), cursor = data.nextCursor;
1153
- button.onclick = async event => {
1154
- if (event?.isTrusted) delete button.dataset.autoLoadFailed;
1155
- button.disabled = true; button.textContent = '正在加载…';
1156
- try {
1157
- const page = await api(`/api/threads/${encodeURIComponent(id)}/history?cursor=${encodeURIComponent(cursor)}`);
1158
- if (!conversation.isCurrent(selection, id)) return;
1159
- const timeline = el('timeline'), anchor = button.nextElementSibling;
1160
- const top = anchor?.getBoundingClientRect().top;
1161
- const boundary = state.timeline.historyGapBeforeTurnId || '';
1162
- const boundaryIndex = boundary ? state.timeline.turns.findIndex(turn => turn.id === boundary) : 0;
1163
- const prefix = boundaryIndex > 0 ? state.timeline.turns.slice(0, boundaryIndex) : [];
1164
- const suffix = boundaryIndex >= 0 ? state.timeline.turns.slice(boundaryIndex) : state.timeline.turns;
1165
- const incoming = Array.isArray(page.turns) ? page.turns : [];
1166
- const incomingIds = new Set(incoming.map(turn => turn.id));
1167
- const overlap = prefix.findIndex(turn => incomingIds.has(turn.id));
1168
- if (boundary && overlap >= 0) {
1169
- state.timeline.turns = [...prefix.slice(0, overlap), ...incoming,
1170
- ...suffix.filter(turn => !incomingIds.has(turn.id))];
1171
- delete state.timeline.historyGapBeforeTurnId;
1172
- } else if (boundary && page.nextCursor) {
1173
- state.timeline.turns = [...prefix, ...incoming, ...suffix];
1174
- state.timeline.historyGapBeforeTurnId = incoming[0]?.id || boundary;
1175
- } else if (boundary) {
1176
- state.timeline.turns = [...incoming, ...suffix];
1177
- delete state.timeline.historyGapBeforeTurnId;
1178
- } else {
1179
- const existing = new Set(state.timeline.turns.map(turn => turn.id));
1180
- state.timeline.turns.unshift(...incoming.filter(turn => !existing.has(turn.id)));
1181
- }
1182
- state.timeline.nextCursor = page.nextCursor;
1183
- timelineRenderer.render(state.timeline, false);
1184
- if (anchor && top !== undefined) {
1185
- const restored = anchor.dataset.turnId
1186
- ? [...timeline.children].find(node => node.dataset.turnId === anchor.dataset.turnId) : null;
1187
- if (restored) timeline.scrollTop += restored.getBoundingClientRect().top - top;
1188
- }
1189
- timelineScroll.markAwayFromBottom(); saveHistoryCache();
1190
- } catch (error) {
1191
- if (!conversation.isCurrent(selection, id)) return;
1192
- if (!event?.isTrusted) button.dataset.autoLoadFailed = 'true';
1193
- button.disabled = false; button.textContent = gap ? '重试加载中间内容' : '重试加载更早消息'; setStatus(error.message);
1194
- }
1195
- };
1196
- requestAnimationFrame(() => {
1197
- if (button.isConnected) maybeAutoLoadHistory();
1198
- });
1199
- return button;
1200
- }
1201
-
1202
- function maybeAutoLoadHistory() {
1203
- const timeline = el('timeline');
1204
- const button = timeline.querySelector('.history-more:not(.timeline-sync-retry)');
1205
- if (!button || button.disabled || button.dataset.autoLoadFailed === 'true') return;
1206
- const viewport = timeline.getBoundingClientRect();
1207
- const trigger = button.getBoundingClientRect();
1208
- const threshold = Math.min(320, Math.max(160, timeline.clientHeight / 3));
1209
- if (trigger.bottom >= viewport.top - threshold && trigger.top <= viewport.bottom + threshold) button.click();
1210
- }
1211
- function lazyOperationGroupElement(entry) {
1212
- const details = document.createElement('details');
1213
- details.className = 'process completed-process lazy-process';
1214
- const summary = document.createElement('summary');
1215
- const label = document.createElement('span'); label.className = 'activity-title';
1216
- label.textContent = entry.title || '执行过程';
1217
- summary.append(activityIcon('tool'), label, disclosureChevronElement());
1218
- const body = document.createElement('div'); body.className = 'process-body';
1219
- details.append(summary, body);
1220
- const renderLoaded = () => {
1221
- body.replaceChildren();
1222
- for (const child of (entry.loadedEntries || []).filter(value => value.kind !== 'reasoning')) {
1223
- const element = renderEntry(child);
1224
- if (element) body.append(element);
1225
- }
1226
- if (!body.childElementCount) body.textContent = '没有可显示的操作明细';
1227
- };
1228
- if (entry.detailsLoaded) renderLoaded();
1229
- const id = state.selectedId, turnId = entry.turnId, groupId = entry.groupId;
1230
- let loading = false;
1231
- details.addEventListener('toggle', async () => {
1232
- if (!details.open || loading || entry.detailsLoaded) return;
1233
- if (!transport.has('pagedHistory') || state.timeline?.cached) {
1234
- body.textContent = '连接工作站后可加载操作明细';
1235
- return;
1236
- }
1237
- const selection = conversation.generation();
1238
- loading = true; body.textContent = '正在加载操作…';
1239
- const loaded = [], loadedIds = new Set(), offsets = new Set();
1240
- let offset = 0, pages = 0;
1241
- try {
1242
- do {
1243
- if (offsets.has(offset)) throw new Error('操作分页位置重复,已停止加载。');
1244
- offsets.add(offset);
1245
- const page = await api(`/api/threads/${encodeURIComponent(id)}/turns/${encodeURIComponent(turnId)}/groups/${encodeURIComponent(groupId)}/items?offset=${offset}`);
1246
- if (!conversation.isCurrent(selection, id)) return;
1247
- const entries = (Array.isArray(page.entries) ? page.entries : []).filter(entry => {
1248
- if (!entry?.id) return true;
1249
- if (loadedIds.has(entry.id)) return false;
1250
- loadedIds.add(entry.id); return true;
1251
- });
1252
- loaded.push(...entries);
1253
- offset = Number.isSafeInteger(page.nextOffset) ? page.nextOffset : null;
1254
- pages++;
1255
- body.textContent = `正在加载操作… ${loaded.length}/${Number(page.total) || entry.count || loaded.length} 项`;
1256
- } while (offset !== null);
1257
- entry.loadedEntries = loaded;
1258
- entry.detailsLoaded = true;
1259
- renderLoaded();
1260
- timelineScroll.markAwayFromBottom();
1261
- reportSyncMetric(`stage=history;action=operation-group;result=loaded;pages=${pages};items=${loaded.length}`);
1262
- } catch (error) {
1263
- if (!conversation.isCurrent(selection, id)) return;
1264
- loading = false; body.textContent = '加载失败,收起后可重试'; setStatus(error.message);
1265
- }
1266
- });
1267
- return disclosures.bind(details, `turn:${turnId}:group:${groupId}`);
1268
- }
1269
- async function restoreRelayConversation() {
1270
- const syncStarted = performance.now();
1271
- const thread = state.selectedThread;
1272
- state.historyLoading = Boolean(thread);
1273
- if (thread && !state.historyProgress) state.historyProgress = '正在同步会话…';
1274
- setConversationConnection('connected');
1275
- if (!thread) { if (!await loadThreads()) throw new Error('对话列表尚未恢复'); return; }
1276
- const selection = conversation.beginSync();
1277
- closeEventStream(); threadContext.stop();
1278
- updateComposerMode();
1279
- let data, unchanged = false, partial = false, listTrusted = false;
1280
- try {
1281
- const recovered = await history.recover(thread.id, state.timeline, () => conversation.isCurrent(selection, thread.id), thread.hostId);
1282
- data = recovered?.timeline;
1283
- unchanged = recovered?.unchanged === true;
1284
- partial = recovered?.partial === true;
1285
- listTrusted = recovered?.listTrusted === true;
1286
- } catch (error) {
1287
- if (conversation.failSync(selection)) {
1288
- updateComposerMode();
1289
- showTimelineSyncFailure(thread, error.message, restoreRelayConversation);
1290
- if (!connectionBlocked()) openEventStream(thread, state.projects.find(project => project.projectId === thread.projectId));
1291
- }
1292
- throw error;
1293
- }
1294
- if (!data) { conversation.failSync(selection); return; }
1295
- const timeline = el('timeline'), atBottom = syncTimelinePosition();
1296
- const anchor = [...timeline.children].find(node => node.dataset.turnId && node.getBoundingClientRect().bottom >= timeline.getBoundingClientRect().top);
1297
- const anchorId = anchor?.dataset.turnId, offset = anchor?.getBoundingClientRect().top;
1298
- if (!conversation.completeSync(selection, data)) return;
1299
- state.timelineAtBottom = atBottom;
1300
- enteredConversation = true;
1301
- const renderStarted = performance.now();
1302
- timelineRenderer.render(data, atBottom);
1303
- attention.markRead(data.thread, 'restore-timeline', data.turns.at(-1)?.id);
1304
- const renderMilliseconds = Math.round(performance.now() - renderStarted);
1305
- if (!atBottom && anchorId) {
1306
- const restored = [...timeline.children].find(node => node.dataset.turnId === anchorId);
1307
- if (restored) timeline.scrollTop += restored.getBoundingClientRect().top - offset;
1308
- }
1309
- history.releaseUnused(data, 'restore');
1310
- setConversationConnection('connected');
1311
- openEventStream(thread, state.projects.find(project => project.projectId === thread.projectId));
1312
- threadContext.start(thread.id); updateComposerMode();
1313
- if (!unchanged) saveHistoryCache();
1314
- reportSyncMetric(`stage=restore;totalMs=${Math.round(performance.now() - syncStarted)};renderMs=${renderMilliseconds};${timelineShapeMetric(data)};unchanged=${unchanged ? 1 : 0};partial=${partial ? 1 : 0};listTrusted=${listTrusted ? 1 : 0}`);
1315
- }
1316
-
1317
- function syncSidebarSelection(thread) {
1318
- taskListView.syncSelection(thread);
1319
- }
1320
-
1321
- function setConversationTitle(title) {
1322
- el('selectedTitle').textContent = title;
1323
- el('terminalChatTitle').textContent = title || 'Code Relax';
1324
- }
1325
- let lastLiveError = '';
1326
- function renderSelectedMeta(project, thread, liveText, connected) {
1327
- if (connected) { lastLiveError = ''; return; }
1328
- if (['正在连接实时更新…', '正在重连…'].includes(liveText)) return;
1329
- if (liveText !== lastLiveError) { showActionToast(liveText, 3000); lastLiveError = liveText; }
1330
- }
1331
-
1332
- function openEventStream(thread, project) {
1333
- closeEventStream();
1334
- setConversationConnection(transport.connectionState() === 'connected' ? 'connected' : 'connecting');
1335
- const source = transport.events(thread, state.timeline?.sync, state.timeline);
1336
- state.eventSource = source;
1337
- requestHostId = thread.hostId;
1338
- source.addEventListener('approvals', event => {
1339
- if (state.eventSource !== source || state.selectedId !== thread.id) return;
1340
- const requests = JSON.parse(event.data).requests || [];
1341
- for (const request of requests) {
1342
- if (request.method === CodexServerRequests.file) {
1343
- request.fileChanges = state.timeline?.turns?.flatMap(turn => turn.entries || []).find(entry => entry.id === request.params.itemId)?.changes;
1344
- }
1345
- }
1346
- approvalPanel.update(requests);
1347
- });
1348
- source.addEventListener('settings', () => {
1349
- if (state.eventSource === source && state.selectedId === thread.id) threadContext.update(thread.id);
1350
- });
1351
- source.addEventListener('thread-list-changed', event => {
1352
- if (state.eventSource !== source) return;
1353
- let change = {};
1354
- try { change = JSON.parse(event.data); } catch {}
1355
- attention.metric('event', { id: change.threadId }, null, 'stream', change.method);
1356
- if (archiveBusy.has(change.threadId)
1357
- && ['thread/archived', 'thread/unarchived'].includes(change.method)) return;
1358
- const selectedSettled = change.threadId === state.selectedId
1359
- && ['turn/completed', 'thread/name/updated'].includes(change.method);
1360
- scheduleThreadRefresh(300, selectedSettled, 'thread-event');
1361
- });
1362
- source.onopen = () => {
1363
- if (state.eventSource !== source || state.selectedId !== thread.id) return;
1364
- setConversationConnection('connected');
1365
- renderSelectedMeta(project, thread, '实时更新', true);
1366
- };
1367
- source.addEventListener('update', event => {
1368
- if (state.eventSource !== source || state.selectedId !== thread.id) return;
1369
- try {
1370
- mergeUpdate(JSON.parse(event.data));
1371
- setConversationConnection('connected');
1372
- } catch (error) {
1373
- renderSelectedMeta(project, thread, `实时数据处理失败:${error.message}`, false);
1374
- if (error.syncGap) recoverSyncGap(thread, source);
1375
- }
1376
- });
1377
- source.addEventListener('queue-changed', async () => {
1378
- try {
1379
- const result = await api(`/api/threads/${encodeURIComponent(thread.id)}/queue`);
1380
- if (state.eventSource === source && state.selectedId === thread.id) queue.render(result.items || []);
1381
- } catch (error) { if (state.eventSource === source) showActionToast(`读取队列失败:${error.message}`); }
1382
- });
1383
- source.addEventListener('bridge-error', event => {
1384
- if (state.eventSource !== source) return;
1385
- let message = 'Bridge 读取失败';
1386
- try { message = JSON.parse(event.data).error || message; } catch {}
1387
- renderSelectedMeta(project, thread, message, false);
1388
- setConversationConnection('disconnected');
1389
- });
1390
- source.onerror = () => {
1391
- if (state.eventSource !== source) return;
1392
- const transportLost = transport.connectionState() !== 'connected';
1393
- setConversationConnection(transportLost && !stationConnection.isPaused() ? 'connecting' : 'disconnected');
1394
- if (transportLost && !stationConnection.isPaused()) void stationConnection.recover();
1395
- };
1396
- }
1397
-
1398
- let syncGapRecovery;
1399
- function recoverSyncGap(thread, source) {
1400
- if (syncGapRecovery || state.eventSource !== source || state.selectedId !== thread.id) return;
1401
- state.historyLoading = true;
1402
- state.historyProgress = '同步出现缺口,正在重新核对…';
1403
- setConversationConnection('connected');
1404
- syncGapRecovery = restoreRelayConversation().catch(error => {
1405
- if (state.selectedId === thread.id) setStatus(`重新同步失败:${error.message}`);
1406
- }).finally(() => { syncGapRecovery = null; });
1407
- }
1408
-
1409
- function closeEventStream() {
1410
- approvalPanel.clear();
1411
- if (state.eventSource) state.eventSource.close();
1412
- state.eventSource = null;
1413
- }
1414
-
1415
- function syncConversationTitle(thread) {
1416
- if (!thread || thread.id !== state.selectedId) return;
1417
- if (!threadTitles.shouldSync(thread)) return;
1418
- const title = resolvedThreadTitle(thread);
1419
- const listed = state.threads.find(item => item.id === thread.id);
1420
- if (state.selectedThread?.title === title && (!listed || listed.title === title)) return;
1421
- if (listed) listed.title = title;
1422
- if (state.selectedThread?.id === thread.id) state.selectedThread.title = title;
1423
- setConversationTitle(title);
1424
- renderTasks();
1425
- }
1426
-
1427
- function mergeUpdate(update) {
1428
- const timelineElement = el('timeline');
1429
- state.timelineAtBottom = syncTimelinePosition();
1430
- const reduced = CodexTimelineReducer.reduceTimelineUpdate(state.timeline, update, {
1431
- recentHistoryLimit: transport.has('recentHistory') ? 10 : 0,
1432
- });
1433
- if (reduced.duplicate) return;
1434
- state.timeline = reduced.timeline;
1435
- if (reduced.reset) {
1436
- if (reduced.notModified) syncConversationTitle(state.timeline.thread);
1437
- else timelineRenderer.render(state.timeline, false);
1438
- saveHistoryCache();
1439
- return;
1440
- }
1441
- if (update.thread) {
1442
- syncConversationTitle(state.timeline.thread);
1443
- const listedThread = state.threads.find(thread => thread.id === state.selectedId);
1444
- const nextActiveFlags = Array.isArray(update.thread.activeFlags) ? update.thread.activeFlags
1445
- : update.thread.status && update.thread.status !== 'active' ? [] : listedThread?.activeFlags;
1446
- const statusChanged = listedThread && update.thread.status && listedThread.status !== update.thread.status;
1447
- const flagsChanged = listedThread && JSON.stringify(listedThread.activeFlags || []) !== JSON.stringify(nextActiveFlags || []);
1448
- if (listedThread && (statusChanged || flagsChanged)) {
1449
- if (update.thread.status) listedThread.status = update.thread.status;
1450
- listedThread.activeFlags = nextActiveFlags || [];
1451
- attention.recordSelectedStatus(listedThread);
1452
- state.selectedThread = listedThread;
1453
- renderTasks();
1454
- }
1455
- }
1456
- if (Array.isArray(update.queue)) queue.render(update.queue);
1457
- pendingMessages.retainImagePreviews(state.selectedId, { turns: reduced.incomingTurns });
1458
- if (reduced.wasEmpty && reduced.addedTurns.length) timelineElement.replaceChildren();
1459
- for (const turn of reduced.addedTurns) timelineElement.append(timelineRenderer.turnElement(turn));
1460
- for (const element of [...timelineElement.children]) {
1461
- if (reduced.removedTurnIds.includes(element.dataset.turnId)) element.remove();
1462
- }
1463
- if (reduced.replacedTurns.length) {
1464
- let missingTurn = false;
1465
- for (const replaceTurn of reduced.replacedTurns) {
1466
- const existing = [...timelineElement.children]
1467
- .find(element => element.classList?.contains('turn') && element.dataset.turnId === replaceTurn.id);
1468
- if (existing) {
1469
- const enteredHistory = existing.dataset.turnStatus === 'inProgress' && replaceTurn.status !== 'inProgress';
1470
- timelineRenderer.replaceTurn(existing, replaceTurn, enteredHistory);
1471
- } else {
1472
- missingTurn = true;
1473
- break;
1474
- }
1475
- }
1476
- if (missingTurn) {
1477
- timelineRenderer.render(state.timeline, false);
1478
- }
1479
- }
1480
- renderPendingMessages();
1481
- timelineRenderer.updateDurations();
1482
- scrollTimelineToBottom();
1483
- updateComposerMode();
1484
- saveHistoryCache();
1485
- if (document.visibilityState === 'visible' && !document.body.classList.contains('sidebar-open')) {
1486
- attention.markRead(state.timeline.thread, 'stream-timeline', state.timeline.turns.at(-1)?.id);
1487
- }
1488
- }
1489
-
1490
- function syncTimelinePosition() {
1491
- return timelineScroll.syncPosition();
1492
- }
1493
-
1494
- function scrollTimelineToBottom(force = false) {
1495
- timelineScroll.scrollToBottom(force);
1496
- }
1497
-
1498
- function updateScrollToBottomButton() {
1499
- timelineScroll.updateButton();
1500
- }
1501
-
1502
- function renderTurnEntries(timeline, turn) {
1503
- const entries = turn.entries || [];
1504
- if (turn.status === 'interrupted') {
1505
- renderInterruptedTurnEntries(timeline, entries);
1506
- return;
1507
- }
1508
- if (turn.status === 'failed') {
1509
- renderFailedTurnEntries(timeline, turn, entries);
1510
- return;
1511
- }
1512
- if (turn.status !== 'inProgress') {
1513
- renderCompletedTurnEntries(timeline, turn, entries);
1514
- return;
1515
- }
1516
- const latestReasoning = reasoningSummaryTexts(entries).at(-1) || '';
1517
- const segments = [];
1518
- const generatedImages = entries.filter(entry => entry.type === 'activity' && entry.kind === 'imageGeneration');
1519
- let generatedImagesRendered = false;
1520
- let processEntries = [];
1521
- const flushProcess = () => {
1522
- if (!processEntries.length) return;
1523
- segments.push({ type: 'process', entries: processEntries });
1524
- processEntries = [];
1525
- };
1526
- for (const entry of entries) {
1527
- const isUser = entry.type === 'message' && entry.role === 'user';
1528
- const isFinal = entry.type === 'message'
1529
- && entry.role === 'assistant'
1530
- && entry.phase === 'final_answer';
1531
- const isGeneratedImage = entry.type === 'activity' && entry.kind === 'imageGeneration';
1532
- if (isUser || isFinal || isGeneratedImage) {
1533
- flushProcess();
1534
- if (isGeneratedImage && generatedImagesRendered) continue;
1535
- const element = isGeneratedImage ? generatedImageGroupElement(generatedImages) : renderEntry(entry);
1536
- if (isGeneratedImage) generatedImagesRendered = true;
1537
- if (element) segments.push({ type: 'element', element, isUser });
1538
- } else {
1539
- processEntries.push(entry);
1540
- }
1541
- }
1542
- flushProcess();
1543
- const lastSegmentIsProcess = segments.at(-1)?.type === 'process';
1544
- const hasUserSegment = segments.some(segment => segment.type === 'element' && segment.isUser);
1545
- let durationPlaced = false;
1546
- if (!hasUserSegment) {
1547
- timeline.append(turnDurationElement(turn));
1548
- durationPlaced = true;
1549
- }
1550
- segments.forEach((segment, index) => {
1551
- if (segment.type === 'element') {
1552
- timeline.append(segment.element);
1553
- if (!durationPlaced && segment.isUser) {
1554
- timeline.append(turnDurationElement(turn));
1555
- durationPlaced = true;
1556
- }
1557
- return;
1558
- }
1559
- appendActiveProcessSegment(
1560
- timeline,
1561
- segment.entries,
1562
- index === segments.length - 1 && lastSegmentIsProcess,
1563
- latestReasoning,
1564
- );
1565
- });
1566
- if (!lastSegmentIsProcess) timeline.append(activeProcessElement([], latestReasoning));
1567
- }
1568
-
1569
- function appendActiveProcessSegment(timeline, entries, keepLastActivityLive, latestReasoning) {
1570
- const units = [];
1571
- let activities = [];
1572
- const flushActivities = () => {
1573
- if (!activities.length) return;
1574
- units.push({ type: 'activities', entries: activities });
1575
- activities = [];
1576
- };
1577
- for (const entry of entries) {
1578
- if (entry.type === 'message') {
1579
- flushActivities();
1580
- units.push({ type: 'message', entry });
1581
- } else if (entry.type === 'activity' && ['contextCompaction', 'imageView', 'imageGeneration', 'lazyOperationGroup'].includes(entry.kind)) {
1582
- flushActivities();
1583
- units.push({ type: 'standalone', entry });
1584
- } else {
1585
- activities.push(entry);
1586
- }
1587
- }
1588
- flushActivities();
1589
- const lastUnitIndex = units.length - 1;
1590
- let liveProcessRendered = false;
1591
- units.forEach((unit, index) => {
1592
- if (unit.type === 'message') {
1593
- const element = renderEntry(unit.entry);
1594
- if (element) timeline.append(element);
1595
- return;
1596
- }
1597
- if (keepLastActivityLive && index === lastUnitIndex
1598
- && !(unit.type === 'standalone' && unit.entry.kind === 'lazyOperationGroup')) {
1599
- const liveEntries = unit.type === 'standalone' ? [unit.entry] : unit.entries;
1600
- timeline.append(activeProcessElement(liveEntries, latestReasoning));
1601
- liveProcessRendered = true;
1602
- return;
1603
- }
1604
- if (unit.type === 'standalone') {
1605
- const element = renderEntry(unit.entry);
1606
- if (element) timeline.append(element);
1607
- return;
1608
- }
1609
- const settled = settledProcessElement(unit.entries);
1610
- if (settled) timeline.append(settled);
1611
- });
1612
- if (keepLastActivityLive && !liveProcessRendered) {
1613
- timeline.append(activeProcessElement([], latestReasoning));
1614
- }
1615
- }
1616
-
1617
- function turnDurationElement(turn) {
1618
- const duration = document.createElement('div');
1619
- duration.className = 'turn-duration process-duration';
1620
- duration.dataset.status = turn.status || 'unknown';
1621
- duration.dataset.startedAt = turn.startedAt || '';
1622
- duration.dataset.completedAt = turn.completedAt || '';
1623
- duration.dataset.durationMs = turn.durationMs ?? '';
1624
- return duration;
1625
- }
1626
-
1627
- function entryDisclosureKey(entry, suffix = '') {
1628
- const identity = entry?.id || [entry?.kind, entry?.server, entry?.tool, entry?.command, entry?.query, entry?.title]
1629
- .filter(Boolean).join(':').slice(0, 160) || 'unknown';
1630
- return `entry:${identity}${suffix ? `:${suffix}` : ''}`;
1631
- }
1632
-
1633
- function processDisclosureKey(entries, fallback = 'unknown') {
1634
- const anchor = entries.find(entry => entry?.id);
1635
- return `process:${anchor?.id || fallback}`;
1636
- }
1637
-
1638
- function settledProcessElement(entries) {
1639
- if (!entries.some(isVisibleProcessActivity)) return null;
1640
- const directActivity = singleSettledActivityElement(entries);
1641
- if (directActivity) return directActivity;
1642
- const details = document.createElement('details');
1643
- details.className = 'process settled-process';
1644
- const summary = document.createElement('summary');
1645
- const text = document.createElement('span');
1646
- text.className = 'activity-title';
1647
- text.textContent = operationSummary(entries);
1648
- summary.append(activityIcon(operationIconKind(entries)), text, disclosureChevronElement());
1649
- const body = document.createElement('div');
1650
- body.className = 'process-body';
1651
- for (const entry of entries.filter(entry => entry.kind !== 'reasoning')) {
1652
- const element = renderEntry(entry);
1653
- if (element) body.append(element);
1654
- }
1655
- const disclosureKey = processDisclosureKey(entries, operationSummary(entries));
1656
- details.append(summary, body);
1657
- return disclosures.bind(details, disclosureKey);
1658
- }
1659
-
1660
- function singleSettledActivityElement(entries) {
1661
- const visible = entries.filter(isVisibleProcessActivity);
1662
- if (visible.length !== 1) return null;
1663
- const entry = visible[0];
1664
- if (entry.kind === 'fileChange' && (entry.changes?.length || 0) !== 1) return null;
1665
- return renderEntry(entry);
1666
- }
1667
-
1668
- function activeProcessElement(entries, latestReasoning = '') {
1669
- const details = document.createElement('details');
1670
- details.className = 'process active-process';
1671
- const summaries = reasoningSummaryTexts(entries);
1672
- const summary = document.createElement('summary');
1673
- const pulse = document.createElement('span');
1674
- pulse.className = 'pulse';
1675
- const text = document.createElement('span');
1676
- text.className = 'activity-title';
1677
- text.textContent = liveStepText(entries, latestReasoning || summaries.at(-1) || '');
1678
- summary.append(pulse, text);
1679
- const body = document.createElement('div');
1680
- body.className = 'process-body';
1681
- for (const entry of entries.filter(entry => entry.transient)) {
1682
- const detail = entry.kind === 'command'
1683
- ? [entry.command, entry.cwd ? `目录:${entry.cwd}` : ''].filter(Boolean).join('\n\n')
1684
- : entry.kind === 'tool' ? [entry.server, entry.tool].filter(Boolean).join('/')
1685
- : entry.kind === 'webSearch' ? entry.query : '';
1686
- if (detail) body.append(activityView.activity('当前操作', detail, entry.status, null, entryDisclosureKey(entry, 'transient')));
1687
- }
1688
- for (const entry of entries.filter(entry => !['reasoning', 'contextCompaction', 'imageView', 'imageGeneration'].includes(entry.kind) && !entry.transient)) {
1689
- const element = renderEntry(entry);
1690
- if (element) body.append(element);
1691
- }
1692
- if (body.childElementCount) {
1693
- summary.append(disclosureChevronElement());
1694
- details.append(summary, body);
1695
- summary.addEventListener('click', event => {
1696
- event.preventDefault();
1697
- details.open = !details.open;
1698
- if (!details.open) return;
1699
- const timeline = el('timeline');
1700
- const viewport = timeline.getBoundingClientRect();
1701
- const expanded = details.getBoundingClientRect();
1702
- // Reveal short details fully; keep the heading visible for long details.
1703
- const distance = Math.min(expanded.bottom - viewport.bottom + 12, expanded.top - viewport.top - 12);
1704
- if (distance > 0) timeline.scrollBy({ top: distance, behavior: 'smooth' });
1705
- });
1706
- } else {
1707
- summary.setAttribute('aria-disabled', 'true');
1708
- summary.tabIndex = -1;
1709
- details.append(summary);
1710
- }
1711
- return disclosures.bind(details, processDisclosureKey(entries, `active:${latestReasoning}`));
1712
- }
1713
-
1714
- function renderInterruptedTurnEntries(timeline, entries) {
1715
- renderLinearTurnEntries(timeline, entries, true);
1716
- }
1717
-
1718
- function renderFailedTurnEntries(timeline, turn, entries) {
1719
- renderLinearTurnEntries(timeline, entries, false);
1720
- timeline.append(turnErrorElement(turn.error));
1721
- }
1722
-
1723
- function renderLinearTurnEntries(timeline, entries, interruptRunning) {
1724
- let activities = [];
1725
- const generatedImages = entries.filter(entry => entry.type === 'activity' && entry.kind === 'imageGeneration');
1726
- let generatedImagesRendered = false;
1727
- const flush = () => {
1728
- if (activities.length) timeline.append(settledProcessElement(activities));
1729
- activities = [];
1730
- };
1731
- for (const original of entries) {
1732
- if (original.kind === 'reasoning') continue;
1733
- const entry = interruptRunning && original.status === 'inProgress' ? { ...original, status: 'interrupted' } : original;
1734
- if (entry.type === 'activity' && !['contextCompaction', 'imageView', 'imageGeneration', 'lazyOperationGroup'].includes(entry.kind)) {
1735
- activities.push(entry);
1736
- } else {
1737
- flush();
1738
- if (entry.kind === 'imageGeneration' && generatedImagesRendered) continue;
1739
- const element = entry.kind === 'imageGeneration' ? generatedImageGroupElement(generatedImages) : renderEntry(entry);
1740
- if (entry.kind === 'imageGeneration') generatedImagesRendered = true;
1741
- if (element) timeline.append(element);
1742
- }
1743
- }
1744
- flush();
1745
- }
1746
-
1747
- function turnErrorElement(error) {
1748
- const element = document.createElement('div');
1749
- element.className = 'turn-error';
1750
- element.setAttribute('role', 'alert');
1751
- const icon = document.createElement('span');
1752
- icon.className = 'turn-error-icon';
1753
- icon.textContent = '!';
1754
- icon.setAttribute('aria-hidden', 'true');
1755
- const message = document.createElement('span');
1756
- message.className = 'turn-error-message';
1757
- message.textContent = turnErrorMessage(error) || '处理失败。';
1758
- element.append(icon, message);
1759
- return element;
1760
- }
1761
-
1762
- function renderCompletedTurnEntries(timeline, turn, entries) {
1763
- const isUser = entry => entry.type === 'message' && entry.role === 'user';
1764
- const isFinal = entry => entry.type === 'message' && entry.role === 'assistant' && entry.phase === 'final_answer';
1765
- const generatedImages = entries.filter(entry => entry.kind === 'imageGeneration');
1766
- for (const entry of entries.filter(isUser)) {
1767
- const element = renderEntry(entry);
1768
- if (element) timeline.append(element);
1769
- }
1770
- const processEntries = entries.filter(entry => !isUser(entry) && !isFinal(entry)
1771
- && entry.kind !== 'imageGeneration' && entry.kind !== 'reasoning');
1772
- if (processEntries.length) timeline.append(processElement(processEntries, turn));
1773
- if (generatedImages.length) timeline.append(generatedImageGroupElement(generatedImages));
1774
- for (const entry of entries.filter(isFinal)) {
1775
- const element = renderEntry(entry);
1776
- if (element) timeline.append(element);
1777
- }
1778
- }
1779
-
1780
- function processElement(entries, turn) {
1781
- const details = document.createElement('details');
1782
- details.className = 'process completed-process';
1783
- const summary = document.createElement('summary');
1784
- const duration = document.createElement('span');
1785
- duration.className = 'process-duration';
1786
- duration.dataset.status = turn.status || 'unknown';
1787
- duration.dataset.startedAt = turn.startedAt || '';
1788
- duration.dataset.completedAt = turn.completedAt || '';
1789
- duration.dataset.durationMs = turn.durationMs ?? '';
1790
- const count = document.createElement('span');
1791
- count.className = 'process-count';
1792
- const commentaryCount = entries.filter(entry => entry.type === 'message').length;
1793
- const visibleActivities = entries.filter(entry => entry.type === 'activity' && entry.kind !== 'reasoning').length;
1794
- const counts = [];
1795
- if (commentaryCount) counts.push(`${commentaryCount} 条进度`);
1796
- if (visibleActivities) counts.push(`${visibleActivities} 项活动`);
1797
- count.textContent = counts.join(' · ');
1798
- summary.append(duration, disclosureChevronElement(), count);
1799
- const body = document.createElement('div');
1800
- body.className = 'process-body';
1801
- let activityGroup = [];
1802
- const appendActivityGroup = () => {
1803
- if (!activityGroup.length) return;
1804
- body.append(settledProcessElement(activityGroup));
1805
- activityGroup = [];
1806
- };
1807
- for (const entry of entries.filter(entry => entry.kind !== 'reasoning')) {
1808
- if (entry.type === 'activity') {
1809
- if (['contextCompaction', 'imageView', 'lazyOperationGroup'].includes(entry.kind)) {
1810
- appendActivityGroup();
1811
- body.append(renderEntry(entry));
1812
- continue;
1813
- }
1814
- activityGroup.push(entry);
1815
- continue;
1816
- }
1817
- appendActivityGroup();
1818
- const element = renderEntry(entry);
1819
- if (element) body.append(element);
1820
- }
1821
- appendActivityGroup();
1822
- details.append(summary, body);
1823
- return disclosures.bind(details, `turn:${turn.id || 'unknown'}:process`, turn.status === 'inProgress');
1824
- }
1825
-
1826
- function liveStepElement(entries, latestReasoning) {
1827
- const element = document.createElement('div');
1828
- element.className = 'live-step';
1829
- const pulse = document.createElement('span');
1830
- pulse.className = 'pulse';
1831
- const text = document.createElement('span');
1832
- text.textContent = liveStepText(entries, latestReasoning);
1833
- element.append(pulse, text);
1834
- return element;
1835
- }
1836
-
1837
- function renderEntry(entry) {
1838
- if (entry.type === 'message' && entry.role === 'assistant' && entry.delivery === 'async') return questionForm(entry);
1839
- if (entry.type === 'message') {
1840
- const isUser = entry.role === 'user';
1841
- const role = isUser
1842
- ? (entry.source === 'remote' ? '你 · Remote' : '你')
1843
- : (entry.phase === 'commentary' ? 'Codex · 过程' : (entry.phase === 'final_answer' ? 'Codex · 结果' : 'Codex'));
1844
- const wrapper = messageElement(isUser ? 'user' : 'agent', entry.text || '', role, entry.imageAssets ||= {}, entry.fileAssets ||= {});
1845
- if (entry.phase === 'commentary') wrapper.classList.add('commentary');
1846
- if (entry.phase === 'final_answer') wrapper.classList.add('final');
1847
- const fragment = document.createDocumentFragment();
1848
- const userImages = [];
1849
- let remoteImages = 0;
1850
- for (const attachment of entry.attachments || []) {
1851
- if (attachment.remotePath) {
1852
- fragment.append(remoteImageElement(attachment)); remoteImages++; continue;
1853
- }
1854
- if ((attachment.type === 'image' || attachment.type === 'localImage') && attachment.available && attachment.url) {
1855
- if (isUser) {
1856
- userImages.push(attachment);
1857
- continue;
1858
- }
1859
- const image = document.createElement('img');
1860
- image.className = 'message-image';
1861
- image.src = authorizedAssetUrl(attachment.thumbnailUrl);
1862
- image.onclick = () => openImageViewer(authorizedAssetUrl(attachment.thumbnailUrl || attachment.url), image.alt,
1863
- () => authorizedAssetUrl(attachment.url));
1864
- image.alt = attachment.name || '用户附图';
1865
- image.loading = 'lazy';
1866
- image.addEventListener('load', () => scrollTimelineToBottom(), { once: true });
1867
- wrapper.append(image);
1868
- continue;
1869
- }
1870
- const badge = document.createElement('span');
1871
- badge.className = 'attachment';
1872
- badge.textContent = attachment.name || ((attachment.type === 'image' || attachment.type === 'localImage') ? '图片不可用' : '附件');
1873
- wrapper.append(badge);
1874
- }
1875
- if (userImages.length) fragment.append(userAttachmentsElement(userImages));
1876
- if (entry.text || (!userImages.length && !remoteImages) || wrapper.querySelector('.attachment')) fragment.append(wrapper);
1877
- if (fragment.childNodes.length) return fragment;
1878
- return wrapper;
1879
- }
1880
-
1881
- if (entry.type !== 'activity') return null;
1882
- if (entry.kind === 'reasoning') return null;
1883
- if (entry.kind === 'lazyOperationGroup') return lazyOperationGroupElement(entry);
1884
- if (entry.kind === 'contextCompaction') return compactActivityElement('上下文已自动压缩', 'compaction');
1885
- if (entry.titleOnly && entry.kind === 'command') {
1886
- const preview = activityPreview(Array.isArray(entry.command) ? entry.command.join(' ') : entry.command);
1887
- return compactActivityElement(`${commandStatusText(entry.status)}${preview ? ` ${preview}` : ''}`, 'command');
1888
- }
1889
- if (entry.titleOnly && entry.kind === 'tool') {
1890
- const source = [entry.server || entry.namespace, entry.tool].filter(Boolean).join('/');
1891
- return compactActivityElement(`${toolStatusText(entry.status)}${source ? ` ${source}` : '工具'}`, 'tool');
1892
- }
1893
- if (entry.titleOnly && entry.kind === 'fileChange') return fileChangeTitleElements(entry);
1894
- if (entry.titleOnly && entry.kind === 'webSearch') {
1895
- return webSearchDetail(entry) ? compactActivityElement(webSearchTitle(entry), 'web') : null;
1896
- }
1897
- if (entry.titleOnly) return compactActivityElement(entry.title || '操作');
1898
- if (entry.kind === 'command') return activityView.command(entry);
1899
- if (entry.kind === 'tool') return activityView.tool(entry);
1900
- if (entry.kind === 'fileChange') {
1901
- return activityView.fileChange(entry);
1902
- }
1903
- if (entry.kind === 'webSearch') {
1904
- return webSearchDetail(entry) ? compactActivityElement(webSearchTitle(entry), 'web') : null;
1905
- }
1906
- if (entry.kind === 'imageView') {
1907
- return imageViewActivityElement(entry);
1908
- }
1909
- if (entry.kind === 'imageGeneration') {
1910
- return generatedImageActivityElement(entry);
1911
- }
1912
- if (entry.kind === 'subAgent') {
1913
- return activityView.activity(`子任务 · ${entry.status || 'unknown'}`, entry.agentPath || entry.agentThreadId || '未提供详情', entry.status);
1914
- }
1915
- if (entry.kind === 'collaboration') {
1916
- const receivers = entry.receiverThreadIds?.length ? `\n\n目标:${entry.receiverThreadIds.join(', ')}` : '';
1917
- return activityView.activity(`任务协作 · ${entry.tool || 'unknown'} · ${entry.status || 'unknown'}`, `${entry.prompt || '无附加消息'}${receivers}`, entry.status);
1918
- }
1919
- return compactActivityElement(entry.title || entry.kind || '事件', 'tool');
1920
- }
1921
-
1922
- const questionDrafts = new Map();
1923
- function questionTurnActive(entry) {
1924
- const turn = state.timeline?.turns?.find(turn => turn.entries?.some(item => item.id === entry.id));
1925
- return turn?.status === 'inProgress';
1926
- }
1927
- function questionForm(entry) {
1928
- const threadId = state.selectedId;
1929
- const allQuestions = entry.questions?.length ? entry.questions.map((question, index) => ({
1930
- ...question, id: JSON.stringify(['request_user_input_async', entry.id, index])
1931
- })) : [{ id: entry.id, title: entry.text, options: [] }];
1932
- const key = `${threadId}/${entry.id}`;
1933
- if (!questionDrafts.has(key)) questionDrafts.set(key, { values: {}, submitted: false, sending: false });
1934
- const draft = questionDrafts.get(key);
1935
- const answers = new Map((state.timeline?.turns || []).flatMap(turn => (turn.entries || [])
1936
- .filter(item => item.role === 'user').flatMap(item => parseQuestionReply(item.text || '') || [])
1937
- .map(reply => [reply.questionItemId, reply.answer])));
1938
- const questions = allQuestions.filter(question => !answers.has(question.id));
1939
- const canAnswer = questionTurnActive(entry) && !draft.submitted && questions.length > 0;
1940
- if (!canAnswer || draft.collapsed || draft.skipped) {
1941
- const history = document.createElement('div'); history.className = 'question-history';
1942
- for (const question of allQuestions) {
1943
- const title = document.createElement('p'); title.textContent = question.title; history.append(title);
1944
- }
1945
- if (canAnswer) {
1946
- const reopen = document.createElement('button'); reopen.type = 'button'; reopen.textContent = '回答问题';
1947
- reopen.onclick = () => {
1948
- if (threadId !== state.selectedId || !questionTurnActive(entry)) return;
1949
- draft.collapsed = false; draft.skipped = false;
1950
- history.replaceWith(questionForm(entry));
1951
- };
1952
- history.append(reopen);
1953
- }
1954
- return history;
1955
- }
1956
- const form = document.createElement('form'); form.className = 'question-form';
1957
- form.dataset.questionState = JSON.stringify([key, questions, draft.sending, draft.submitted]);
1958
- const fields = document.createElement('fieldset');
1959
- const submit = document.createElement('button'); submit.type = 'submit';
1960
- const header = document.createElement('div'); header.className = 'question-header';
1961
- const caption = document.createElement('span'); caption.textContent = 'ⓘ 问题';
1962
- const close = document.createElement('button'); close.type = 'button'; close.setAttribute('aria-label', '关闭问题'); close.textContent = '×';
1963
- const dismiss = skipped => {
1964
- draft.collapsed = true; draft.skipped = skipped;
1965
- form.replaceWith(questionForm(entry));
1966
- };
1967
- close.onclick = () => dismiss(false); header.append(caption, close);
1968
- fields.disabled = threadReadOnly() || draft.sending || draft.submitted || questions.every(q => answers.has(q.id));
1969
- for (const question of questions) {
1970
- const group = document.createElement('div'); group.className = 'question-group';
1971
- const title = document.createElement('div'); title.textContent = question.title; group.append(title);
1972
- const input = document.createElement('textarea'); input.placeholder = '或自行撰写回复';
1973
- input.setAttribute('aria-label', question.title); input.rows = 1;
1974
- input.value = answers.get(question.id) ?? draft.values[question.id] ?? '';
1975
- const choices = document.createElement('div'); choices.className = 'question-choices';
1976
- const update = () => {
1977
- draft.values[question.id] = input.value;
1978
- for (const button of choices.children) button.setAttribute('aria-pressed', String(button.textContent === input.value));
1979
- submit.disabled = questions.some(q => !(draft.values[q.id] || '').trim());
1980
- };
1981
- for (const option of question.options || []) {
1982
- const button = document.createElement('button'); button.type = 'button'; button.textContent = option;
1983
- button.setAttribute('aria-pressed', String(option === input.value));
1984
- button.onclick = () => { input.value = option; update(); }; choices.append(button);
1985
- }
1986
- input.oninput = update; group.append(choices, input); fields.append(group);
1987
- }
1988
- const footer = document.createElement('div'); footer.className = 'question-footer';
1989
- const skip = document.createElement('button'); skip.type = 'button'; skip.textContent = '跳过';
1990
- skip.onclick = () => dismiss(true);
1991
- submit.disabled = questions.some(q => !(draft.values[q.id] || '').trim());
1992
- submit.textContent = fields.disabled ? (threadReadOnly() ? '只读' : draft.sending ? '正在提交…' : '已回答') : '发送';
1993
- footer.append(skip, submit); fields.append(footer);
1994
- form.append(fields, header);
1995
- form.onsubmit = async event => {
1996
- event.preventDefault();
1997
- if (threadId !== state.selectedId || !questionTurnActive(entry) || connectionBlocked() || threadReadOnly() || state.historyLoading || state.timeline?.cached || state.sending || draft.sending || draft.submitted || fields.disabled) return;
1998
- const replies = questions.map(q => ({ questionItemId: q.id, question: q.title, answer: (draft.values[q.id] || '').trim() }));
1999
- if (replies.some(reply => !reply.answer)) { showActionToast('请填写回答或选择选项'); return; }
2000
- draft.sending = true; fields.disabled = true; state.sending = true; updatePromptActions();
2001
- submit.textContent = '正在提交…';
2002
- try {
2003
- await api(`/api/threads/${encodeURIComponent(threadId)}/messages`, { method: 'POST', body: JSON.stringify({
2004
- prompt: `<send_user_message_question_reply>\n${JSON.stringify(replies)}\n</send_user_message_question_reply>`,
2005
- hostId: state.selectedThread?.hostId
2006
- }) });
2007
- draft.submitted = true;
2008
- if (threadId === state.selectedId) form.replaceWith(questionForm(entry));
2009
- } catch (error) { fields.disabled = false; submit.textContent = '发送'; showActionToast(`回答提交未确认:${error.message}`); }
2010
- finally { draft.sending = false; state.sending = false; updatePromptActions(); }
2011
- };
2012
- return form;
2013
- }
2014
- function reportSyncMetric(detail) {
2015
- try { window.codexNative?.reportSyncMetric?.(detail); } catch {}
2016
- }
2017
-
2018
- function timelineShapeMetric(data) {
2019
- const turns = Array.isArray(data?.turns) ? data.turns : [];
2020
- const allEntries = turns.flatMap(turn => Array.isArray(turn.entries) ? turn.entries : []);
2021
- const entries = allEntries.length;
2022
- const messages = allEntries.filter(entry => entry?.type === 'message').length;
2023
- const groups = allEntries.filter(entry => entry?.kind === 'lazyOperationGroup').length;
2024
- const active = turns.filter(turn => turn.status === 'inProgress').length;
2025
- return `turns=${turns.length};entries=${entries};messages=${messages};groups=${groups};active=${active};more=${data?.nextCursor ? 1 : 0};gap=${data?.historyGapBeforeTurnId ? 1 : 0}`;
2026
- }
2027
- function messageElement(kind, text, role, imageAssets = {}, fileAssets = {}) {
2028
- return messageView.render(kind, text, role, imageAssets, fileAssets);
2029
- }
2030
-
2031
- const remoteImageLoads = new Map();
2032
- const remoteImageObserver = new IntersectionObserver(entries => {
2033
- for (const entry of entries) {
2034
- if (!entry.target.isConnected) { remoteImageObserver.unobserve(entry.target); remoteImageLoads.delete(entry.target); }
2035
- else if (entry.isIntersecting) { remoteImageObserver.unobserve(entry.target); remoteImageLoads.get(entry.target)?.(); }
2036
- }
2037
- }, { root: el('timeline'), rootMargin: '100px' });
2038
- messageView = window.CodexMessageView.createMessageView({
2039
- document,
2040
- markdown,
2041
- enhanceCodeBlocks,
2042
- remoteImageElement,
2043
- openImageViewer,
2044
- linkActions,
2045
- isEmbedded: () => platform.embedded,
2046
- scrollTimelineToBottom,
2047
- });
2048
- timelineRenderer = window.createTimelineRenderer({
2049
- document,
2050
- getTimeline: () => el('timeline'),
2051
- getSelectedThreadId: () => state.selectedId,
2052
- disclosures,
2053
- pendingMessages,
2054
- historyButton,
2055
- renderTurnEntries,
2056
- renderEmptyMessage: () => messageElement('agent', '这个任务暂时没有可显示的对话。', 'Bridge'),
2057
- renderPendingMessages,
2058
- scrollTimelineToBottom,
2059
- turnLabel,
2060
- formatDuration,
2061
- processDurationMs,
2062
- remoteImageObserver,
2063
- remoteImageLoads,
2064
- onAuthoritativeTimeline: syncConversationTitle,
2065
- });
2066
- function imagePlaceholderGraphic() {
2067
- const namespace = 'http://www.w3.org/2000/svg';
2068
- const svg = document.createElementNS(namespace, 'svg');
2069
- svg.setAttribute('viewBox', '0 0 24 24');
2070
- svg.setAttribute('aria-hidden', 'true');
2071
- for (const [tag, attributes] of [
2072
- ['rect', { x: '3', y: '4', width: '18', height: '16', rx: '2' }],
2073
- ['circle', { cx: '8.5', cy: '9', r: '1.5' }],
2074
- ['path', { d: 'm5 17 4.5-4 3 3 2.5-2 4 3' }],
2075
- ]) {
2076
- const node = document.createElementNS(namespace, tag);
2077
- for (const [name, value] of Object.entries(attributes)) node.setAttribute(name, value);
2078
- svg.append(node);
2079
- }
2080
- return svg;
2081
- }
2082
- function imageRefreshGraphic() {
2083
- const namespace = 'http://www.w3.org/2000/svg';
2084
- const svg = document.createElementNS(namespace, 'svg');
2085
- svg.setAttribute('viewBox', '0 0 24 24');
2086
- svg.setAttribute('aria-hidden', 'true');
2087
- for (const d of ['M20 11a8 8 0 0 0-14.9-4', 'M4 4v5h5', 'M4 13a8 8 0 0 0 14.9 4', 'M20 20v-5h-5']) {
2088
- const path = document.createElementNS(namespace, 'path');
2089
- path.setAttribute('d', d); svg.append(path);
2090
- }
2091
- return svg;
2092
- }
2093
- function showImageRetry(button, retry, error) {
2094
- button.disabled = false;
2095
- button.classList.add('image-placeholder');
2096
- button.classList.remove('loading');
2097
- const art = document.createElement('span');
2098
- art.className = 'image-placeholder-art'; art.append(imagePlaceholderGraphic());
2099
- const refresh = document.createElement('span');
2100
- refresh.className = 'image-placeholder-refresh'; refresh.append(imageRefreshGraphic());
2101
- button.replaceChildren(art, refresh);
2102
- button.setAttribute('aria-label', '重新加载图片');
2103
- button.title = error?.message ? `重新加载图片:${error.message}` : '重新加载图片';
2104
- button.onclick = async () => {
2105
- if (button.disabled) return;
2106
- button.disabled = true; button.classList.add('loading');
2107
- button.setAttribute('aria-label', '正在重新加载图片');
2108
- showActionToast('正在重新加载图片…');
2109
- try {
2110
- await retry();
2111
- } catch (nextError) {
2112
- showImageRetry(button, retry, nextError);
2113
- showActionToast(`图片仍无法读取:${nextError.message}`);
2114
- }
2115
- };
2116
- }
2117
- function remoteImageElement(attachment, inline = false, alt = '图片', generated = false, gallery = null) {
2118
- const container = document.createElement(inline ? 'span' : 'div');
2119
- container.className = inline ? 'remote-inline-image' : generated ? 'generated-image remote-attachment-image' : 'user-attachments remote-attachment-image';
2120
- container.dataset.remotePath = attachment.remotePath; container.dataset.imageAlt = alt;
2121
- const button = document.createElement('button'); button.type = 'button'; button.className = 'image-thumb'; button.textContent = '加载图片';
2122
- container.append(button);
2123
- const imageUrlKind = value => /^blob:/i.test(value || '') ? 'blob'
2124
- : /^data:/i.test(value || '') ? 'data' : /^https?:/i.test(value || '') ? 'http' : value ? 'other' : 'empty';
2125
- const recoveryTurnId = () => attachment.recoveryTurnId || container.closest('.turn')?.dataset.turnId || '';
2126
- const recoverBrokenImage = () => {
2127
- reportSyncMetric(`stage=image;action=load-error;kind=${imageUrlKind(attachment.thumbnailUrl || attachment.url)};remote=${attachment.remotePath ? 1 : 0}`);
2128
- attachment.thumbnailUrl = '';
2129
- container.replaceChildren(button);
2130
- showImageRetry(button, attachment.remotePath ? loadRemoteThumbnail : loadEmbeddedThumbnail, new Error('图片显示失败'));
2131
- };
2132
- const updateMatchingImages = fields => {
2133
- Object.assign(attachment, fields);
2134
- for (const turn of state.timeline?.turns || []) for (const entry of turn.entries || []) for (const image of [...(entry.attachments || []), ...Object.values(entry.imageAssets || {}), entry.image].filter(Boolean)) {
2135
- if (image.remotePath === attachment.remotePath) Object.assign(image, fields);
2136
- }
2137
- };
2138
- const loadOriginal = async () => {
2139
- try {
2140
- let url = attachment.url;
2141
- if (!url && attachment.remotePath) {
2142
- url = await transport.readImage(state.selectedId, attachment.remotePath, 'original', attachment.recoveryToken, recoveryTurnId());
2143
- updateMatchingImages({ available: true, url });
2144
- saveHistoryCache();
2145
- }
2146
- return url;
2147
- } catch (error) {
2148
- setStatus('原图读取失败:' + error.message);
2149
- throw error;
2150
- }
2151
- };
2152
- const show = () => {
2153
- const previewUrl = attachment.thumbnailUrl || attachment.url;
2154
- if (generated) {
2155
- const previewButton = document.createElement('button'); previewButton.type = 'button'; previewButton.className = 'generated-image-button';
2156
- previewButton.title = '查看原图';
2157
- const image = document.createElement('img'); image.className = 'generated-image-preview';
2158
- image.src = previewUrl; image.alt = alt; image.loading = gallery ? 'eager' : 'lazy';
2159
- image.addEventListener('error', recoverBrokenImage, { once: true });
2160
- if (gallery) gallery.ready(previewUrl, image);
2161
- else image.addEventListener('load', () => scrollTimelineToBottom(), { once: true });
2162
- previewButton.onclick = () => openImageViewer(previewUrl, alt, loadOriginal);
2163
- previewButton.append(image); container.replaceChildren(previewButton); return;
2164
- }
2165
- if (!inline) {
2166
- container.replaceChildren(...userAttachmentsElement([attachment], recoverBrokenImage,
2167
- (image, label) => openImageViewer(image.thumbnailUrl || image.url, label, loadOriginal)).childNodes);
2168
- return;
2169
- }
2170
- const image = document.createElement('img'); image.className = 'message-image';
2171
- image.src = previewUrl; image.alt = alt; image.loading = 'lazy'; image.tabIndex = 0; image.setAttribute('role', 'button');
2172
- image.addEventListener('error', recoverBrokenImage, { once: true });
2173
- image.onclick = () => openImageViewer(previewUrl, alt, loadOriginal);
2174
- image.onkeydown = event => { if (['Enter', ' '].includes(event.key)) { event.preventDefault(); openImageViewer(previewUrl, alt, loadOriginal); } };
2175
- image.addEventListener('load', () => scrollTimelineToBottom(), { once: true }); container.replaceChildren(image);
2176
- };
2177
- async function loadEmbeddedThumbnail() {
2178
- if (!attachment.url) throw new Error('原图片已经不可用');
2179
- button.disabled = true; button.classList.add('loading');
2180
- button.setAttribute('aria-label', '正在重新加载图片');
2181
- const url = await createDisplayThumbnail(attachment.url);
2182
- attachment.thumbnailUrl = url;
2183
- if (button.isConnected) show();
2184
- saveHistoryCache();
2185
- }
2186
- const threadId = state.selectedId, selection = conversation.generation();
2187
- async function loadRemoteThumbnail() {
2188
- remoteImageLoads.delete(button); button.disabled = true; button.classList.add('loading');
2189
- button.setAttribute('aria-label', '正在重新加载图片');
2190
- const url = await transport.readImage(threadId, attachment.remotePath, 'thumbnail', attachment.recoveryToken, recoveryTurnId());
2191
- if (!conversation.isCurrent(selection, threadId) || !button.isConnected) return;
2192
- updateMatchingImages({ available: true, thumbnailUrl: url });
2193
- show(); saveHistoryCache();
2194
- }
2195
- if (!attachment.remotePath && attachment.url && attachment.thumbnailUrl === attachment.url
2196
- && /^data:image\//i.test(attachment.url)) {
2197
- button.disabled = true; button.textContent = '正在生成缩略图…';
2198
- void loadEmbeddedThumbnail().catch(error => showImageRetry(button, loadEmbeddedThumbnail, error));
2199
- return container;
2200
- }
2201
- if (attachment.thumbnailUrl || attachment.url) { show(); return container; }
2202
- const load = () => loadRemoteThumbnail().catch(error => {
2203
- if (conversation.isCurrent(selection, threadId) && button.isConnected) showImageRetry(button, loadRemoteThumbnail, error);
2204
- });
2205
- button.onclick = load;
2206
- if (gallery) gallery.loads.push(load);
2207
- else { remoteImageLoads.set(button, load); remoteImageObserver.observe(button); }
2208
- return container;
2209
- }
2210
- function userAttachmentsElement(attachments, onImageError = null, onOpen = null) {
2211
- const container = document.createElement('div');
2212
- container.className = 'user-attachments';
2213
- for (const attachment of attachments) {
2214
- const url = authorizedAssetUrl(attachment.url);
2215
- const button = document.createElement('button');
2216
- button.className = 'image-thumb';
2217
- button.type = 'button';
2218
- button.title = attachment.name || '查看完整图片';
2219
- button.setAttribute('aria-label', attachment.name || '查看完整图片');
2220
- const image = document.createElement('img');
2221
- image.alt = attachment.name || '用户附图';
2222
- image.loading = 'lazy';
2223
- image.addEventListener('error', () => {
2224
- if (onImageError) { onImageError(); return; }
2225
- reportSyncMetric(`stage=image;action=load-error;kind=${/^blob:/i.test(image.src) ? 'blob' : /^data:/i.test(image.src) ? 'data' : /^https?:/i.test(image.src) ? 'http' : 'other'};remote=${attachment.remotePath ? 1 : 0}`);
2226
- showImageRetry(button, loadThumbnail, new Error('图片显示失败'));
2227
- }, { once: true });
2228
- image.addEventListener('load', () => scrollTimelineToBottom(), { once: true });
2229
- const display = thumbnail => {
2230
- button.disabled = false; button.classList.remove('image-placeholder', 'loading');
2231
- button.onclick = () => onOpen ? onOpen(attachment, image.alt, button)
2232
- : openImageViewer(attachment.thumbnailUrl || url, image.alt, () => url);
2233
- image.src = authorizedAssetUrl(thumbnail); button.replaceChildren(image);
2234
- };
2235
- async function loadThumbnail() {
2236
- if (!url) throw new Error('原图片已经不可用');
2237
- button.disabled = true; button.classList.add('loading');
2238
- const thumbnail = await createDisplayThumbnail(url);
2239
- attachment.thumbnailUrl = thumbnail; display(thumbnail); saveHistoryCache();
2240
- }
2241
- if (!attachment.remotePath && url && attachment.thumbnailUrl === attachment.url && /^data:image\//i.test(url)) {
2242
- button.textContent = '正在生成缩略图…'; button.disabled = true;
2243
- void loadThumbnail().catch(error => showImageRetry(button, loadThumbnail, error));
2244
- } else display(attachment.thumbnailUrl || attachment.url);
2245
- container.append(button);
2246
- }
2247
- return container;
2248
- }
2249
-
2250
- let imageViewerOriginalLoader = null;
2251
- let imageViewerOriginalUrl = '';
2252
- function setImageViewerOriginalState(label, disabled) {
2253
- const button = el('imageViewerOriginal');
2254
- button.disabled = disabled;
2255
- button.querySelector('.image-viewer-action-label').textContent = label;
2256
- button.setAttribute('aria-label', label);
2257
- button.title = label;
2258
- }
2259
- function openImageViewer(url, alt, loadOriginal = null) {
2260
- const viewer = el('imageViewer');
2261
- const image = el('imageViewerImage');
2262
- image.src = url;
2263
- image.alt = alt || '完整图片';
2264
- viewer.dataset.downloadName = alt || '图片';
2265
- imageViewerOriginalLoader = loadOriginal;
2266
- imageViewerOriginalUrl = loadOriginal ? '' : url;
2267
- setImageViewerOriginalState(loadOriginal ? '查看原图' : '已是原图', !loadOriginal);
2268
- if (!viewer.open) viewer.showModal();
2269
- }
2270
-
2271
- async function ensureViewerOriginal() {
2272
- if (imageViewerOriginalUrl) return imageViewerOriginalUrl;
2273
- if (!imageViewerOriginalLoader) return el('imageViewerImage').src;
2274
- setImageViewerOriginalState('正在加载原图…', true);
2275
- try {
2276
- const url = await imageViewerOriginalLoader();
2277
- if (!url) throw new Error('原图不可用');
2278
- imageViewerOriginalUrl = url;
2279
- el('imageViewerImage').src = url;
2280
- setImageViewerOriginalState('已是原图', true);
2281
- return url;
2282
- } catch (error) {
2283
- setImageViewerOriginalState('重新查看原图', false);
2284
- throw error;
2285
- }
2286
- }
2287
-
2288
- function createDisplayThumbnail(url) {
2289
- return new Promise((resolve, reject) => {
2290
- const source = new Image();
2291
- source.onload = () => {
2292
- const scale = Math.min(1, 640 / Math.max(source.naturalWidth, source.naturalHeight));
2293
- const canvas = document.createElement('canvas');
2294
- canvas.width = Math.max(1, Math.round(source.naturalWidth * scale));
2295
- canvas.height = Math.max(1, Math.round(source.naturalHeight * scale));
2296
- const context = canvas.getContext('2d');
2297
- if (!context) { reject(new Error('无法生成图片缩略图')); return; }
2298
- context.drawImage(source, 0, 0, canvas.width, canvas.height);
2299
- resolve(canvas.toDataURL('image/webp', 0.78));
2300
- };
2301
- source.onerror = () => reject(new Error('图片解码失败'));
2302
- source.src = url;
2303
- });
2304
- }
2305
-
2306
- async function downloadViewerImage() {
2307
- const button = el('imageViewerDownload');
2308
- if (button.disabled) return;
2309
- button.disabled = true; button.classList.add('loading');
2310
- button.setAttribute('aria-label', '正在保存原图'); button.title = '正在保存原图';
2311
- try {
2312
- const url = await ensureViewerOriginal();
2313
- if (!url) throw new Error('原图不可用');
2314
- const response = await fetch(url);
2315
- if (!response.ok) throw new Error(`图片读取失败(${response.status})`);
2316
- const blob = await response.blob();
2317
- const extension = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/webp': 'webp', 'image/gif': 'gif', 'image/bmp': 'bmp' }[blob.type] || 'png';
2318
- const base = (el('imageViewer').dataset.downloadName || '图片').replace(/[\\/:*?"<>|]/g, '_').slice(0, 80) || '图片';
2319
- const name = `${base}.${extension}`;
2320
- if (window.codexNative?.saveDownloadedFile) {
2321
- const bytes = new Uint8Array(await blob.arrayBuffer()); let binary = '';
2322
- for (let offset = 0; offset < bytes.length; offset += 16384) binary += String.fromCharCode(...bytes.subarray(offset, offset + 16384));
2323
- window.codexNative.saveDownloadedFile(name, btoa(binary));
2324
- } else {
2325
- const href = URL.createObjectURL(blob), anchor = document.createElement('a');
2326
- anchor.href = href; anchor.download = name; anchor.click();
2327
- setTimeout(() => URL.revokeObjectURL(href), 1000);
2328
- }
2329
- showActionToast('图片已保存');
2330
- } catch (error) {
2331
- showActionToast('图片保存失败:' + error.message, 3000);
2332
- } finally {
2333
- button.disabled = false; button.classList.remove('loading');
2334
- button.setAttribute('aria-label', '下载原图'); button.title = '下载原图';
2335
- }
2336
- }
2337
-
2338
- function compactActivityElement(text, iconKind = null) {
2339
- const element = document.createElement('div');
2340
- element.className = 'activity compact';
2341
- if (iconKind) element.append(activityIcon(iconKind));
2342
- const label = document.createElement('span');
2343
- label.className = 'activity-title';
2344
- label.textContent = text;
2345
- element.append(label);
2346
- return element;
2347
- }
2348
-
2349
- function fileChangeTitleElements(entry) {
2350
- const changes = Array.isArray(entry.changes) ? entry.changes : [];
2351
- if (!changes.length) return compactActivityElement('已编辑文件', 'edit');
2352
- const fragment = document.createDocumentFragment();
2353
- const completed = ['completed', 'failed'].includes(statusText(entry.status));
2354
- for (const change of changes) {
2355
- const row = document.createElement('div');
2356
- row.className = 'activity compact';
2357
- row.append(activityIcon('edit'));
2358
- const label = document.createElement('span');
2359
- label.className = 'activity-title';
2360
- const filename = String(change.path || '文件').split(/[\\/]/).filter(Boolean).at(-1) || '文件';
2361
- label.textContent = `${completed ? '已编辑' : '正在编辑'} ${filename}`;
2362
- row.append(label);
2363
- const counts = diffLineCounts(typeof change.diff === 'string' ? change.diff : change.diff?.text);
2364
- if (counts.added) {
2365
- const added = document.createElement('span');
2366
- added.className = 'diff-add';
2367
- added.textContent = `+${counts.added}`;
2368
- row.append(added);
2369
- }
2370
- if (counts.removed) {
2371
- const removed = document.createElement('span');
2372
- removed.className = 'diff-remove';
2373
- removed.textContent = `-${counts.removed}`;
2374
- row.append(removed);
2375
- }
2376
- fragment.append(row);
2377
- }
2378
- return fragment;
2379
- }
2380
-
2381
- function imageViewActivityElement(entry) {
2382
- const image = entry.image;
2383
- if (!image || (!image.remotePath && (!image.available || !image.url))) return compactActivityElement('已查看 1 张图像', 'image');
2384
- const details = document.createElement('details');
2385
- details.className = 'activity image-view-activity';
2386
- const summary = document.createElement('summary');
2387
- const label = document.createElement('span');
2388
- label.className = 'activity-title';
2389
- label.textContent = '已查看 1 张图像';
2390
- summary.append(activityIcon('image'), label, disclosureChevronElement());
2391
- const body = document.createElement('div');
2392
- body.className = 'image-view-detail';
2393
- if (image.remotePath) {
2394
- body.append(remoteImageElement(image, false, image.name || '已查看图像', true));
2395
- details.append(summary, body);
2396
- return disclosures.bind(details, entryDisclosureKey(entry));
2397
- }
2398
- const button = document.createElement('button');
2399
- button.className = 'image-view-thumb';
2400
- button.type = 'button';
2401
- button.title = '查看原图';
2402
- const preview = document.createElement('img');
2403
- const url = authorizedAssetUrl(image.url);
2404
- preview.src = authorizedAssetUrl(image.thumbnailUrl);
2405
- preview.alt = image.name || '已查看图像';
2406
- preview.loading = 'lazy';
2407
- preview.addEventListener('load', () => scrollTimelineToBottom(), { once: true });
2408
- button.onclick = () => openImageViewer(preview.src, preview.alt, () => url);
2409
- button.append(preview);
2410
- body.append(button);
2411
- details.append(summary, body);
2412
- return disclosures.bind(details, entryDisclosureKey(entry));
2413
- }
2414
-
2415
- function generatedImageActivityElement(entry) {
2416
- if (entry.failure) {
2417
- const message = typeof entry.failure === 'string' ? entry.failure : entry.failure.message;
2418
- const element = compactActivityElement('图片生成失败', 'image');
2419
- if (message) element.title = message;
2420
- return element;
2421
- }
2422
- if (!entry.image) return compactActivityElement(entry.status === 'inProgress' ? '正在生成图片…' : '生成的图片不可用', 'image');
2423
- return remoteImageElement(entry.image, false, '生成的图片', true);
2424
- }
2425
-
2426
- const generatedImageSelections = new Map();
2427
- const generatedImageAspectRatios = new Map();
2428
- function generatedImageGroupElement(entries) {
2429
- if (entries.length <= 1) return generatedImageActivityElement(entries[0]);
2430
- const key = JSON.stringify([state.selectedId, entries[0]?.id]);
2431
- const container = document.createElement('div');
2432
- container.className = 'generated-image-group multiple';
2433
- const stack = document.createElement('div');
2434
- stack.className = 'generated-image-stack';
2435
- const referenceIndex = entries.findIndex(entry => entry.image && !entry.failure);
2436
- const referenceImage = entries[referenceIndex]?.image;
2437
- const knownRatio = generatedImageAspectRatios.get(key)
2438
- || (referenceImage?.width > 0 && referenceImage?.height > 0 ? referenceImage.width / referenceImage.height : null);
2439
- if (knownRatio) stack.style.aspectRatio = String(knownRatio);
2440
- const controls = document.createElement('div');
2441
- controls.className = 'generated-image-controls';
2442
- controls.setAttribute('role', 'group');
2443
- controls.setAttribute('aria-label', '选择生成的图片');
2444
- const loads = [];
2445
- const thumbnails = [];
2446
- const slides = entries.map((entry, index) => {
2447
- const thumbnail = document.createElement('button');
2448
- thumbnail.type = 'button';
2449
- thumbnail.className = 'generated-image-thumbnail';
2450
- thumbnail.setAttribute('aria-label', `第 ${index + 1} 张图片,共 ${entries.length} 张`);
2451
- thumbnail.textContent = entry.failure ? '失败' : entry.status === 'inProgress' ? '生成中' : `${index + 1}`;
2452
- thumbnail.onclick = () => show(index);
2453
- thumbnail.onkeydown = event => {
2454
- const target = { ArrowUp: index - 1, ArrowDown: index + 1, Home: 0, End: entries.length - 1 }[event.key];
2455
- if (target === undefined) return;
2456
- event.preventDefault();
2457
- show(target);
2458
- thumbnails[selected].focus({ preventScroll: true });
2459
- };
2460
- thumbnails.push(thumbnail);
2461
- controls.append(thumbnail);
2462
- const slide = document.createElement('div');
2463
- slide.className = 'generated-image-slide';
2464
- const content = entry.image && !entry.failure
2465
- ? remoteImageElement(entry.image, false, '生成的图片', true, {
2466
- loads,
2467
- ready: (url, image) => {
2468
- if (index === referenceIndex && !knownRatio) {
2469
- const reserveSize = () => {
2470
- if (image.naturalWidth > 0 && image.naturalHeight > 0) {
2471
- const ratio = image.naturalWidth / image.naturalHeight;
2472
- generatedImageAspectRatios.set(key, ratio);
2473
- stack.style.aspectRatio = String(ratio);
2474
- }
2475
- };
2476
- if (image.complete) reserveSize();
2477
- else image.addEventListener('load', reserveSize, { once: true });
2478
- }
2479
- const preview = document.createElement('img');
2480
- preview.alt = '';
2481
- preview.src = url;
2482
- thumbnail.replaceChildren(preview);
2483
- }
2484
- })
2485
- : generatedImageActivityElement(entry);
2486
- if (content) slide.append(content);
2487
- stack.append(slide);
2488
- return slide;
2489
- });
2490
- let selected = Math.max(0, entries.findIndex(entry => entry.id === generatedImageSelections.get(key)));
2491
- const show = index => {
2492
- selected = Math.max(0, Math.min(index, slides.length - 1));
2493
- slides.forEach((slide, slideIndex) => { slide.hidden = slideIndex !== selected; });
2494
- thumbnails.forEach((thumbnail, thumbnailIndex) => {
2495
- thumbnail.setAttribute('aria-pressed', String(thumbnailIndex === selected));
2496
- });
2497
- const thumbnail = thumbnails[selected];
2498
- if (thumbnail.offsetTop < controls.scrollTop || thumbnail.offsetTop + thumbnail.offsetHeight > controls.scrollTop + controls.clientHeight) {
2499
- controls.scrollTop = thumbnail.offsetTop - (controls.clientHeight - thumbnail.offsetHeight) / 2;
2500
- }
2501
- generatedImageSelections.set(key, entries[selected].id);
2502
- };
2503
- container.append(stack, controls);
2504
- if (loads.length) {
2505
- remoteImageLoads.set(stack, () => {
2506
- remoteImageLoads.delete(stack);
2507
- loads.forEach(load => load());
2508
- });
2509
- remoteImageObserver.observe(stack);
2510
- }
2511
- show(selected);
2512
- return container;
2513
- }
2514
-
2515
- function activityIcon(kind) {
2516
- const namespace = 'http://www.w3.org/2000/svg';
2517
- const wrapper = document.createElement('span');
2518
- wrapper.className = 'activity-icon';
2519
- wrapper.setAttribute('aria-hidden', 'true');
2520
- const svg = document.createElementNS(namespace, 'svg');
2521
- const usesFilledCodexAsset = kind === 'web' || kind === 'image' || kind === 'compaction';
2522
- if (usesFilledCodexAsset) wrapper.classList.add('filled');
2523
- svg.setAttribute('viewBox', usesFilledCodexAsset ? '0 0 16 16' : '0 0 24 24');
2524
- const add = (tag, attributes) => {
2525
- const node = document.createElementNS(namespace, tag);
2526
- for (const [name, value] of Object.entries(attributes)) node.setAttribute(name, value);
2527
- svg.append(node);
2528
- };
2529
- if (kind === 'command') {
2530
- add('path', { d: 'm7 11 2-2-2-2' });
2531
- add('path', { d: 'M11 13h4' });
2532
- add('rect', { width: '18', height: '18', x: '3', y: '3', rx: '2', ry: '2' });
2533
- } else if (kind === 'edit') {
2534
- add('path', { d: 'M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z' });
2535
- add('path', { d: 'm15 5 4 4' });
2536
- } else if (kind === 'web') {
2537
- add('path', { 'fill-rule': 'evenodd', 'clip-rule': 'evenodd', d: 'M8 1.47461C11.6037 1.47461 14.5254 4.39634 14.5254 8C14.5254 11.6037 11.6037 14.5254 8 14.5254C4.39634 14.5254 1.47461 11.6037 1.47461 8C1.47461 4.39634 4.39634 1.47461 8 1.47461ZM5.87012 8.52539C5.92564 9.91134 6.19693 11.1363 6.59375 12.0293C6.81716 12.532 7.06946 12.9068 7.32324 13.1484C7.57428 13.3873 7.80269 13.4745 8 13.4746C8.19723 13.4745 8.42487 13.3871 8.67578 13.1484C8.92962 12.9069 9.18182 12.532 9.40527 12.0293C9.80215 11.1363 10.0742 9.91142 10.1299 8.52539H5.87012ZM2.55078 8.52539C2.74797 10.5966 4.09925 12.3291 5.9541 13.0771C5.83897 12.8833 5.73221 12.6753 5.63477 12.4561C5.16951 11.4092 4.87546 10.0335 4.81934 8.52539H2.55078ZM11.1797 8.52539C11.1237 10.0335 10.8305 11.4092 10.3652 12.4561C10.2677 12.6755 10.1603 12.8832 10.0449 13.0771C11.9002 12.3293 13.252 10.5969 13.4492 8.52539H11.1797ZM5.9541 2.92188C4.09908 3.66986 2.74798 5.40329 2.55078 7.47461H4.81934C4.87546 5.96653 5.16951 4.59081 5.63477 3.54395C5.73233 3.32446 5.8388 3.11594 5.9541 2.92188ZM8 2.52539C7.80269 2.52546 7.57428 2.61265 7.32324 2.85156C7.06946 3.09315 6.81716 3.46804 6.59375 3.9707C6.19693 4.86365 5.92564 6.08866 5.87012 7.47461H10.1299C10.0742 6.08859 9.80215 4.86367 9.40527 3.9707C9.18182 3.46795 8.92962 3.09313 8.67578 2.85156C8.42487 2.61285 8.19723 2.52549 8 2.52539ZM10.0449 2.92188C10.1605 3.11604 10.2676 3.32427 10.3652 3.54395C10.8305 4.59082 11.1237 5.96649 11.1797 7.47461H13.4492C13.252 5.40298 11.9004 3.66967 10.0449 2.92188Z' });
2538
- } else if (kind === 'image') {
2539
- add('path', { d: 'M8.71388 7.99975C8.71388 8.65789 8.18035 9.19142 7.52222 9.19142C6.86408 9.19142 6.33055 8.65789 6.33055 7.99975C6.33055 7.34162 6.86408 6.80809 7.52222 6.80809C8.18035 6.80809 8.71388 7.34162 8.71388 7.99975Z' });
2540
- add('path', { d: 'M3.14066 10.4099C3.86911 9.44319 5.24342 9.25004 6.2101 9.97849L10.3385 13.0895L10.0217 13.5087L9.70588 13.9279L5.57747 10.8169C5.07391 10.4375 4.35836 10.538 3.97892 11.0415L2.63994 12.8184L2.22081 12.5026L1.80155 12.1858L3.14066 10.4099Z' });
2541
- add('path', { d: 'M9.67744 6.75438C9.5741 6.02085 8.89571 5.51025 8.16209 5.61335L3.27651 6.29998C2.54313 6.40329 2.03252 7.08091 2.13535 7.81435L2.82197 12.6999C2.92507 13.4336 3.60289 13.9449 4.33648 13.8421L9.22206 13.1554C9.95584 13.0523 10.4672 12.3737 10.3641 11.64L9.67744 6.75438ZM11.4036 11.4939C11.5875 12.8019 10.6762 14.0112 9.36817 14.195L4.48259 14.8817C3.17474 15.0652 1.96523 14.1541 1.78142 12.8462L1.09479 7.96059C0.911255 6.65289 1.82264 5.44345 3.13027 5.25942L8.01585 4.5728C9.32373 4.38899 10.533 5.30049 10.717 6.60828L11.4036 11.4939Z' });
2542
- add('path', { d: 'M13.5112 9.53334L14.1978 4.64776C14.3006 3.91417 13.7893 3.23635 13.0556 3.13325L8.17007 2.44663C7.43662 2.3438 6.75901 2.85441 6.65569 3.58779L6.35329 5.7395L5.31273 5.59326L5.61513 3.44155C5.79916 2.13392 7.0086 1.22253 8.31631 1.40607L13.2019 2.09269C14.5098 2.2765 15.4209 3.48602 15.2374 4.79387L14.5507 9.67945C14.3669 10.9875 13.1576 11.8988 11.8496 11.7149L10.2829 11.4948L10.429 10.4552L11.9957 10.6753C12.7294 10.7785 13.408 10.2671 13.5112 9.53334Z' });
2543
- } else if (kind === 'compaction') {
2544
- add('path', { d: 'M11.5247 1.80859C12.9925 1.80873 14.1826 2.99992 14.1829 4.46777V11.752C14.6643 11.9563 15.0021 12.4342 15.0022 12.9902C15.0021 13.7322 14.4004 14.3346 13.6584 14.335C12.9162 14.335 12.3139 13.7324 12.3137 12.9902C12.3138 12.4342 12.6513 11.9563 13.1331 11.752V4.46777C13.1328 3.57982 12.4126 2.85951 11.5247 2.85938L8.86646 2.86035C8.57667 2.86035 8.34133 2.62469 8.34106 2.33496C8.34106 2.04501 8.57651 1.80859 8.86646 1.80859H11.5247Z' });
2545
- add('path', { d: 'M2.33228 1.75488C3.0744 1.75501 3.67589 2.35651 3.67603 3.09863C3.67603 3.65479 3.33827 4.13233 2.85669 4.33691L2.85767 11.5234C2.85793 12.4115 3.57797 13.1318 4.46606 13.1318H7.12427C7.41399 13.1321 7.64966 13.3674 7.64966 13.6572C7.64939 13.9468 7.41383 14.1824 7.12427 14.1826H4.46606C2.99807 14.1826 1.80813 12.9914 1.80786 11.5234L1.80688 4.33691C1.32538 4.13221 0.987549 3.65459 0.987549 3.09863C0.987681 2.35642 1.59004 1.75488 2.33228 1.75488Z' });
2546
- add('path', { d: 'M8.66626 8.89258C8.95621 8.89258 9.19165 9.12802 9.19165 9.41797C9.19165 9.70792 8.95621 9.94336 8.66626 9.94336H5.66626C5.37648 9.94316 5.14087 9.7078 5.14087 9.41797C5.14087 9.12814 5.37648 8.89278 5.66626 8.89258H8.66626Z' });
2547
- add('path', { d: 'M10.3333 6.05957C10.6231 6.05975 10.8577 6.29512 10.8577 6.58496C10.8575 6.87469 10.623 7.11018 10.3333 7.11035H5.66626C5.37656 7.11015 5.141 6.87468 5.14087 6.58496C5.14087 6.29513 5.37648 6.05977 5.66626 6.05957H10.3333Z' });
2548
- } else {
2549
- add('path', { d: 'M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z' });
2550
- }
2551
- wrapper.append(svg);
2552
- return wrapper;
2553
- }
2554
-
2555
- function disclosureChevronElement() {
2556
- const chevron = document.createElement('span');
2557
- chevron.className = 'disclosure-chevron';
2558
- chevron.setAttribute('aria-hidden', 'true');
2559
- const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
2560
- svg.setAttribute('viewBox', '0 0 24 24');
2561
- const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
2562
- path.setAttribute('d', 'm9 18 6-6-6-6');
2563
- svg.append(path);
2564
- chevron.append(svg);
2565
- return chevron;
2566
- }
2567
-
2568
- function authorizedAssetUrl(value) { return transport.assetUrl(value); }
2569
-
2570
- const stoppingThreads = new Set();
2571
- function composerIsActive() {
2572
- return composer.isActive();
2573
- }
2574
- function composerShouldStop() {
2575
- return composer.shouldStop();
2576
- }
2577
- function updatePendingMessageElement(message) {
2578
- if (!message.element) {
2579
- const row = document.createElement('div'); row.className = 'pending-message';
2580
- row.append(renderEntry({ ...message, type: 'message', role: 'user' }));
2581
- const status = document.createElement('div'); status.className = 'pending-message-status';
2582
- row.append(status); message.element = row; message.statusElement = status;
2583
- const actions = document.createElement('div'); actions.className = 'pending-message-actions';
2584
- const retry = document.createElement('button'); retry.textContent = '重新发送';
2585
- retry.onclick = () => resendPendingMessage(message);
2586
- const remove = document.createElement('button'); remove.textContent = '删除';
2587
- remove.onclick = () => {
2588
- if (message.retrying) return;
2589
- pendingMessages.set(state.selectedId, pendingMessages.get(state.selectedId).filter(item => item !== message));
2590
- renderPendingMessages();
2591
- };
2592
- actions.append(retry, remove); row.append(actions);
2593
- message.actionsElement = actions; message.retryElement = retry; message.removeElement = remove;
2594
- }
2595
- message.statusElement.textContent = message.status;
2596
- message.statusElement.hidden = !message.status;
2597
- if (message.actionsElement) {
2598
- const canRetry = Boolean(state.selectedId && pendingMessages.has(state.selectedId, message));
2599
- message.actionsElement.hidden = !message.status || !canRetry;
2600
- message.retryElement.disabled = Boolean(message.retrying || state.sending || connectionBlocked() || state.historyLoading);
2601
- message.retryElement.textContent = message.retrying ? '正在核对…' : '重新发送';
2602
- message.removeElement.disabled = Boolean(message.retrying);
2603
- }
2604
- return message.element;
2605
- }
2606
- function renderPendingMessages() {
2607
- const timeline = el('timeline');
2608
- const { remaining, interruptedUnconfirmed } = pendingMessages.reconcile(state.selectedId, state.timeline);
2609
- if (interruptedUnconfirmed) showActionToast('任务已停止,插队消息未在对话中确认');
2610
- const turnElements = [...timeline.children].filter(node => node.classList.contains('turn'));
2611
- for (const message of remaining) {
2612
- const owner = message.turnId ? turnElements.find(node => node.dataset?.turnId === message.turnId) : null;
2613
- const activeSummary = (owner || (!message.turnId && composerIsActive() ? turnElements.at(-1) : null))?.querySelector('.active-process');
2614
- updatePendingMessageElement(message);
2615
- if (activeSummary) activeSummary.before(message.element);
2616
- else if (owner) owner.append(message.element);
2617
- else timeline.append(message.element);
2618
- }
2619
- timeline.querySelectorAll('.pending-message').forEach(node => { if (!remaining.some(message => message.element === node)) node.remove(); });
2620
- }
2621
- async function resendPendingMessage(message) {
2622
- return messageSender.resend(message);
2623
- }
2624
-
2625
- function updateComposerMode() {
2626
- return composer.updateMode();
2627
- }
2628
-
2629
- function messageBody(prompt) {
2630
- return messageSender.createBody(prompt);
2631
- }
2632
-
2633
- async function sendMessage(mode = 'direct') {
2634
- return messageSender.send(mode);
2635
- }
2636
-
2637
- let appLogSending = false;
2638
- async function sendAppLogDirect(text, threadId, thread) {
2639
- appLogSending = true;
2640
- try {
2641
- const clientUserMessageId = Array.from(crypto.getRandomValues(new Uint8Array(16)),
2642
- byte => byte.toString(16).padStart(2, '0')).join('');
2643
- await api(`/api/threads/${encodeURIComponent(threadId)}/messages`, {
2644
- method: 'POST',
2645
- body: JSON.stringify({
2646
- prompt: `Code Relax 应用日志(不含凭据、二维码和消息正文):\n\n\`\`\`text\n${text}\n\`\`\``,
2647
- clientUserMessageId,
2648
- images: [],
2649
- hostId: thread?.hostId,
2650
- }),
2651
- });
2652
- showActionToast('应用日志已发送到当前对话', 3000);
2653
- } catch (error) {
2654
- showActionToast(`应用日志发送失败:${error.message}`, 5000);
2655
- } finally {
2656
- appLogSending = false;
2657
- }
2658
- }
2659
-
2660
- window.harmonySendAppLog = value => {
2661
- const text = typeof value === 'string' ? value.trim() : '';
2662
- if (!text) { showActionToast('暂无可发送的应用日志'); return false; }
2663
- if (!state.selectedId || isNewThread()) { showActionToast('请先进入一个已有对话'); return false; }
2664
- if (appLogSending) { showActionToast('应用日志正在发送'); return false; }
2665
- if (transport.connectionState() !== 'connected') { showActionToast('工作站链路尚未连接,暂时不能发送日志'); return false; }
2666
- const threadId = state.selectedId;
2667
- const thread = state.selectedThread || state.threads.find(item => item.id === threadId);
2668
- document.body.classList.remove('sidebar-open');
2669
- void sendAppLogDirect(text, threadId, thread);
2670
- return true;
2671
- };
2672
-
2673
- function closeComposerAddMenu() {
2674
- composer.closeAddMenu();
2675
- }
2676
-
2677
- function setSendControlsDisabled(disabled) {
2678
- composer.setControlsDisabled(disabled);
2679
- }
2680
-
2681
- function updatePromptActions() {
2682
- composer.updateActions();
2683
- }
2684
-
2685
- function renderContextUsage(usage) {
2686
- const meter = el('contextMeter');
2687
- state.currentModel = usage?.model || '';
2688
- state.currentThinking = usage?.thinking || '';
2689
- state.currentServiceTier = usage?.serviceTier ?? null;
2690
- state.currentPermissionMode = usage?.permissionMode ?? null;
2691
- state.permissionModes = Array.isArray(usage?.permissionModes) ? usage.permissionModes : [];
2692
- if (el('modelMenu').hidden) resetModelMenuToCurrent();
2693
- meter.classList.remove('warning', 'danger');
2694
- if (!usage?.available) {
2695
- meter.style.setProperty('--context-percent', '0');
2696
- meter.title = '当前任务的上下文占用暂不可用';
2697
- meter.setAttribute('aria-label', meter.title);
2698
- renderModelControl();
2699
- return;
2700
- }
2701
- const percent = Math.round(usage.usedPercent);
2702
- meter.style.setProperty('--context-percent', String(percent));
2703
- if (percent >= 90) meter.classList.add('danger');
2704
- else if (percent >= 75) meter.classList.add('warning');
2705
- meter.title = `上下文 ${percent}% · ${compactTokens(usage.usedTokens)} / ${compactTokens(usage.contextWindow)}`;
2706
- meter.setAttribute('aria-label', meter.title);
2707
- renderModelControl();
2708
- }
2709
-
2710
- function modelDisplayName(value) {
2711
- const option = [...el('model').options].find(item => item.value === value);
2712
- return option?.textContent || value;
2713
- }
2714
-
2715
- function thinkingDisplayName(value) {
2716
- const names = { none: '无', minimal: '最低', low: '轻度', medium: '中', high: '高', xhigh: '极高', max: '最高', ultra: 'Ultra' };
2717
- return names[value] || value;
2718
- }
2719
-
2720
- function compactTokens(value) {
2721
- const numeric = Number(value);
2722
- if (!Number.isFinite(numeric)) return '--';
2723
- if (numeric >= 1000) return `${Math.round(numeric / 100) / 10}K`;
2724
- return String(Math.round(numeric));
2725
- }
2726
-
2727
- let serverModels = [];
2728
- let modelMenuRequest = 0;
2729
- let enabledReasoningEfforts = null;
2730
- let applyingServerSettings = false;
2731
- async function applyServerSettings(serviceTier) {
2732
- if (connectionBlocked()) return;
2733
- if (threadReadOnly()) { setStatus(state.timeline.thread.readOnlyReason || '该对话当前只能查看'); return; }
2734
- if (state.historyLoading || state.timeline?.cached) { setStatus('请等待对话同步完成后再调整设置'); return; }
2735
- if (isNewThread()) {
2736
- state.currentModel = el('model').value || state.currentModel; state.currentThinking = state.menuThinking;
2737
- if (serviceTier !== undefined) state.currentServiceTier = serviceTier;
2738
- renderModelControl(); renderFastMode(); return;
2739
- }
2740
- if (!transport.has('immediateSettings') || applyingServerSettings || !state.selectedId) return;
2741
- const threadId = state.selectedId, model = el('model').value || state.currentModel, thinking = state.menuThinking;
2742
- if (model === state.currentModel && thinking === state.currentThinking && (serviceTier === undefined || serviceTier === state.currentServiceTier)) return;
2743
- applyingServerSettings = true;
2744
- renderFastMode();
2745
- renderPermissionControl();
2746
- el('modelMenu').setAttribute('aria-busy', 'true');
2747
- try {
2748
- const context = await api(`/api/threads/${encodeURIComponent(threadId)}/settings`, { method: 'POST', body: JSON.stringify({ model, thinking, ...(serviceTier !== undefined ? { serviceTier } : {}) }) });
2749
- if (state.selectedId === threadId) { renderContextUsage(context); resetModelMenuToCurrent(); }
2750
- } catch (error) {
2751
- setStatus(`设置失败:${error.message}`);
2752
- if (state.selectedId === threadId) resetModelMenuToCurrent();
2753
- } finally { applyingServerSettings = false; el('modelMenu').removeAttribute('aria-busy'); renderFastMode(); renderPermissionControl(); }
2754
- }
2755
- async function loadServerModels(isCurrent) {
2756
- const result = await api('/api/models');
2757
- if (!isCurrent()) return;
2758
- if (!result.models?.length) throw new Error('工作站未返回可用模型');
2759
- serverModels = result.models;
2760
- enabledReasoningEfforts = Array.isArray(result.enabledReasoningEfforts) ? result.enabledReasoningEfforts : null;
2761
- const select = el('model'); select.replaceChildren();
2762
- for (const button of el('modelView').querySelectorAll('.model-option')) button.remove();
2763
- for (const model of serverModels) {
2764
- const option = document.createElement('option'); option.value = model.model; option.textContent = model.displayName || model.model; select.append(option);
2765
- const button = document.createElement('button'); button.type = 'button'; button.className = 'model-option'; button.dataset.model = model.model;
2766
- const label = document.createElement('span'); label.textContent = option.textContent;
2767
- const check = document.createElement('span'); check.className = 'check'; button.append(label, check);
2768
- el('modelView').insertBefore(button, el('modelView').querySelector('.menu-divider'));
2769
- }
2770
- if (isNewThread() && !state.currentModel) {
2771
- const model = serverModels.find(item => item.isDefault) || serverModels[0];
2772
- state.currentModel = model.model; state.currentThinking = model.defaultReasoningEffort;
2773
- }
2774
- }
2775
-
2776
- function updateThinkingOptions(render = true) {
2777
- const effectiveModel = el('model').value || state.currentModel;
2778
- if (transport.has('immediateSettings') && serverModels.length) {
2779
- const model = serverModels.find(item => item.model === effectiveModel);
2780
- const efforts = (model?.supportedReasoningEfforts || []).filter(item => !enabledReasoningEfforts || enabledReasoningEfforts.includes(item.reasoningEffort));
2781
- const select = el('thinking'), track = el('effortTrack').querySelector('.effort-options');
2782
- select.replaceChildren(); track.replaceChildren();
2783
- for (const [index, item] of efforts.entries()) {
2784
- const option = document.createElement('option'); option.value = item.reasoningEffort; option.textContent = thinkingDisplayName(option.value); select.append(option);
2785
- const button = document.createElement('button'); button.type = 'button'; button.className = 'effort-option'; button.dataset.thinking = option.value;
2786
- button.style.left = `${100 * index / Math.max(1, efforts.length - 1)}%`;
2787
- button.title = option.textContent; button.setAttribute('aria-label', option.textContent); track.append(button);
2788
- }
2789
- if (!efforts.some(item => item.reasoningEffort === state.menuThinking)) {
2790
- state.menuThinking = efforts.find(item => item.reasoningEffort === model?.defaultReasoningEffort)?.reasoningEffort || efforts[0]?.reasoningEffort || state.currentThinking;
2791
- }
2792
- select.value = state.menuThinking;
2793
- if (render) renderModelControl();
2794
- return;
2795
- }
2796
- const restricted = effectiveModel === 'gpt-5.5' || effectiveModel === 'gpt-5.3-codex-spark';
2797
- for (const option of el('thinking').options) {
2798
- option.disabled = restricted && (option.value === 'max' || option.value === 'ultra');
2799
- }
2800
- for (const button of document.querySelectorAll('.effort-option')) {
2801
- button.disabled = restricted && (button.dataset.thinking === 'max' || button.dataset.thinking === 'ultra');
2802
- }
2803
- if (state.menuThinking && el('thinking').selectedOptions[0]?.disabled) {
2804
- state.menuThinking = 'xhigh';
2805
- el('thinking').value = state.menuThinking;
2806
- }
2807
- if (render) renderModelControl();
2808
- }
2809
-
2810
- function renderModelControl() {
2811
- renderFastMode();
2812
- renderPermissionControl();
2813
- const selectedModel = el('model').value || state.currentModel;
2814
- const selectedThinking = state.menuThinking || state.currentThinking || 'medium';
2815
- const currentModelName = state.currentModel ? modelDisplayName(state.currentModel) : isNewThread() ? '默认模型' : '读取当前模型…';
2816
- const currentThinkingName = state.currentThinking ? thinkingDisplayName(state.currentThinking) : '';
2817
- el('modelSummaryName').textContent = currentModelName;
2818
- el('modelSummaryEffort').textContent = currentThinkingName;
2819
- el('modelSummaryEffort').classList.toggle('ultra', state.currentThinking === 'ultra');
2820
- el('effortView').classList.toggle('ultra', selectedThinking === 'ultra');
2821
- el('effortEntryText').classList.toggle('ultra', selectedThinking === 'ultra');
2822
- const pending = state.pendingSettingsByThread.get(state.selectedId);
2823
- const pendingModelName = pending?.model ? modelDisplayName(pending.model) : '';
2824
- const pendingThinkingName = pending?.thinking ? thinkingDisplayName(pending.thinking) : '';
2825
- el('pendingSettingsText').textContent = pending
2826
- ? `下一条切换为:${[pendingModelName, pendingThinkingName].filter(Boolean).join(' · ')}`
2827
- : '';
2828
- el('pendingSettings').hidden = !pending;
2829
- el('effortEntryText').textContent = thinkingDisplayName(selectedThinking);
2830
- el('effortHeadingValue').textContent = thinkingDisplayName(selectedThinking);
2831
- el('effortModelName').textContent = selectedModel ? modelDisplayName(selectedModel) : '读取模型…';
2832
- const selectedModelOption = selectedModel;
2833
- for (const button of document.querySelectorAll('.model-option')) {
2834
- const selected = button.dataset.model === selectedModelOption;
2835
- button.classList.toggle('selected', selected);
2836
- button.querySelector('.check').textContent = selected ? '✓' : '';
2837
- }
2838
- const effortButtons = [...document.querySelectorAll('.effort-option')];
2839
- const selectedIndex = Math.max(0, effortButtons.findIndex(button => button.dataset.thinking === selectedThinking));
2840
- const denominator = Math.max(1, effortButtons.length - 1);
2841
- const ratio = selectedIndex / denominator;
2842
- el('effortTrack').style.setProperty('--effort-progress', `${ratio * 100}%`);
2843
- el('effortTrack').style.setProperty('--effort-offset', `${20 - ratio * 40}px`);
2844
- el('effortTrack').style.setProperty('--effort-range-offset', `${14 - ratio * 28}px`);
2845
- for (const [index, button] of effortButtons.entries()) {
2846
- button.classList.toggle('selected', button.dataset.thinking === selectedThinking);
2847
- button.classList.toggle('reached', index <= selectedIndex);
2848
- }
2849
- }
2850
-
2851
- function resetModelMenuToCurrent() {
2852
- if (transport.has('immediateSettings') && serverModels.length) {
2853
- const pending = state.pendingSettingsByThread.get(state.selectedId);
2854
- el('model').value = pending?.model || state.currentModel;
2855
- state.menuThinking = pending?.thinking || state.currentThinking;
2856
- updateThinkingOptions(false); renderModelControl(); return;
2857
- }
2858
- const currentModelOption = [...el('model').options].find(option => option.value === state.currentModel);
2859
- el('model').value = currentModelOption ? state.currentModel : '';
2860
- const currentThinkingOption = [...el('thinking').options].find(option => option.value === state.currentThinking);
2861
- state.menuThinking = currentThinkingOption ? state.currentThinking : 'medium';
2862
- el('thinking').value = state.menuThinking;
2863
- updateThinkingOptions(false);
2864
- renderModelControl();
2865
- }
2866
-
2867
- function updatePendingSettingsFromMenu() {
2868
- if (isNewThread()) { state.currentModel = el('model').value || state.currentModel; state.currentThinking = state.menuThinking; renderModelControl(); return; }
2869
- if (!state.selectedId) return;
2870
- if (transport.has('immediateSettings')) { renderModelControl(); return; }
2871
- const model = el('model').value || state.currentModel;
2872
- const thinking = state.menuThinking || state.currentThinking;
2873
- const unchanged = (!model || model === state.currentModel)
2874
- && (!thinking || thinking === state.currentThinking);
2875
- if (unchanged) state.pendingSettingsByThread.delete(state.selectedId);
2876
- else state.pendingSettingsByThread.set(state.selectedId, { model, thinking });
2877
- renderModelControl();
2878
- }
2879
-
2880
- function showModelView() {
2881
- el('modelView').hidden = false;
2882
- el('effortView').hidden = true;
2883
- }
2884
-
2885
- function showEffortView() {
2886
- el('modelView').hidden = true;
2887
- el('effortView').hidden = false;
2888
- }
2889
-
2890
- function closeModelMenu() {
2891
- modelMenuRequest++;
2892
- el('modelMenu').hidden = true;
2893
- el('modelMenu').inert = false;
2894
- el('modelMenu').removeAttribute('data-loading');
2895
- el('modelMenu').removeAttribute('aria-busy');
2896
- el('modelTrigger').setAttribute('aria-expanded', 'false');
2897
- }
2898
-
2899
- function closePermissionMenu() {
2900
- el('permissionMenu').hidden = true;
2901
- el('permissionTrigger').setAttribute('aria-expanded', 'false');
2902
- }
2903
-
2904
- function renderPermissionControl() {
2905
- const supported = transport.has('permissionSettings') && state.permissionModes.length > 0;
2906
- const mode = state.currentPermissionMode;
2907
- const labels = { ask: '请求批准', auto: '帮我批准', full: '完全访问' };
2908
- el('permissionControl').hidden = !supported;
2909
- if (!supported) closePermissionMenu();
2910
- el('permissionLabel').textContent = labels[mode] || '权限';
2911
- el('permissionTrigger').title = `操作权限:${labels[mode] || '自定义'}`;
2912
- el('permissionTrigger').setAttribute('aria-label', el('permissionTrigger').title);
2913
- el('permissionTrigger').classList.toggle('full-access', mode === 'full');
2914
- const blocked = connectionBlocked() || threadReadOnly() || state.historyLoading || state.timeline?.cached || applyingServerSettings;
2915
- el('permissionTrigger').disabled = Boolean(blocked);
2916
- for (const button of el('permissionMenu').querySelectorAll('[data-permission]')) {
2917
- button.hidden = !state.permissionModes.includes(button.dataset.permission);
2918
- button.disabled = Boolean(blocked);
2919
- button.setAttribute('aria-checked', String(button.dataset.permission === mode));
2920
- }
2921
- }
2922
-
2923
- async function applyPermissionMode(mode) {
2924
- if (!transport.has('permissionSettings') || connectionBlocked() || threadReadOnly() || applyingServerSettings || state.historyLoading || state.timeline?.cached || !state.selectedId || !state.permissionModes.includes(mode)) return;
2925
- if (mode === state.currentPermissionMode) { closePermissionMenu(); return; }
2926
- const threadId = state.selectedId, selection = conversation.generation();
2927
- applyingServerSettings = true;
2928
- renderPermissionControl(); renderFastMode();
2929
- try {
2930
- const context = await api(`/api/threads/${encodeURIComponent(threadId)}/permissions`, { method: 'POST', body: JSON.stringify({ mode }) });
2931
- if (conversation.isCurrent(selection, threadId)) { renderContextUsage(context); closePermissionMenu(); }
2932
- } catch (error) {
2933
- if (conversation.isCurrent(selection, threadId)) setStatus(`权限设置失败:${error.message}`);
2934
- } finally {
2935
- applyingServerSettings = false; renderPermissionControl(); renderFastMode();
2936
- }
2937
- }
2938
-
2939
- el('permissionTrigger').onclick = () => {
2940
- if (el('permissionTrigger').disabled) return;
2941
- const open = el('permissionMenu').hidden;
2942
- closeModelMenu();
2943
- el('permissionMenu').hidden = !open;
2944
- el('permissionTrigger').setAttribute('aria-expanded', String(open));
2945
- };
2946
- el('permissionMenu').onclick = event => {
2947
- const button = event.target.closest('[data-permission]');
2948
- if (button && !button.disabled) applyPermissionMode(button.dataset.permission);
2949
- };
2950
-
2951
- function fastServiceTier() {
2952
- const model = serverModels.find(item => item.model === (el('model').value || state.currentModel));
2953
- return model?.serviceTiers?.find(tier => ['priority', 'fast'].includes(tier.id))?.id;
2954
- }
2955
-
2956
- function renderFastMode() {
2957
- const supported = transport.has('immediateSettings') && Boolean(fastServiceTier());
2958
- const enabled = ['priority', 'fast'].includes(state.currentServiceTier);
2959
- el('modelFastIndicator').hidden = !enabled;
2960
- el('effortView').classList.toggle('fast', enabled);
2961
- const button = el('fastMode');
2962
- button.disabled = !supported || connectionBlocked() || threadReadOnly() || state.historyLoading || state.timeline?.cached || applyingServerSettings;
2963
- button.setAttribute('aria-checked', String(enabled));
2964
- button.title = supported ? '快速模式,开启会增加用量' : transport.has('immediateSettings') ? '当前模型暂不支持快速模式' : '基础控制暂不支持修改快速模式';
2965
- button.setAttribute('aria-label', button.title);
2966
- }
2967
-
2968
- el('reconnectConversation').onclick = async () => {
2969
- if (state.connection === 'connecting') return;
2970
- stationConnection.activate();
2971
- await stationConnection.recover();
2972
- };
2973
- window.addEventListener('offline', () => setConversationConnection('disconnected'));
2974
- window.addEventListener('online', () => { if (!enteredConversation && window.codexSelfRelayConfig && !stationConnection.isPaused()) void enterConversation(); if (enteredConversation && connectionBlocked()) void stationConnection.recover(); });
2975
-
2976
- const sidebarTitle = document.querySelector('.sidebar-brand > span');
2977
- let lastTitleClick = null;
2978
- sidebarTitle.addEventListener('click', event => {
2979
- if (lastTitleClick !== null && event.timeStamp - lastTitleClick <= 350) {
2980
- lastTitleClick = null;
2981
- openLatestDebugPage();
2982
- return;
2983
- }
2984
- lastTitleClick = event.timeStamp;
2985
- });
2986
- el('selectedTitle').ondblclick = () => void refreshCurrentConversation();
2987
- el('terminalChatTitle').ondblclick = () => void refreshCurrentConversation();
2988
- el('refresh').onclick = refreshTaskList;
2989
- el('archivedThreads').onclick = () => void showArchivedThreads();
2990
- el('newChat').onclick = () => {
2991
- communityView?.close();
2992
- if (state.showingArchived) { state.showingArchived = false; renderTasks(); }
2993
- const projectId = state.selectedThread?.projectId
2994
- || (isNewThread() ? state.newThreadProject?.projectId : null) || '__unassigned__';
2995
- openNewThreadDialog(state.projects.find(project => project.projectId === projectId), projectId);
2996
- };
2997
- el('clearNewThreadProject').onclick = () => openNewThreadDialog(null, '__unassigned__');
2998
- function queueActionVisible() {
2999
- return composer.queueActionVisible();
3000
- }
3001
- function queueChoiceAvailable() {
3002
- return composer.queueChoiceAvailable();
3003
- }
3004
- el('send').onclick = () => {
3005
- if (composerShouldStop()) interruptCurrentTurn(); else sendMessage('direct');
3006
- };
3007
- el('sendQueued').onclick = () => {
3008
- if (queueChoiceAvailable()) sendMessage('queue');
3009
- };
3010
- async function interruptCurrentTurn() {
3011
- const threadId = state.selectedId;
3012
- if (!threadId) return;
3013
- const available = actionState('interrupt', stoppingThreads.has(threadId));
3014
- if (!available.enabled) { setStatus(available.reason); return; }
3015
- const turnId = state.timeline?.turns?.at(-1)?.id;
3016
- stoppingThreads.add(threadId); updateComposerMode();
3017
- setStatus('正在请求停止…');
3018
- try {
3019
- await api(`/api/threads/${encodeURIComponent(threadId)}/interrupt`, { method: 'POST', body: JSON.stringify({ turnId }) });
3020
- if (state.selectedId === threadId) {
3021
- const stopped = state.timeline?.turns?.find(turn => turn.id === turnId)?.status === 'interrupted';
3022
- setStatus(stopped ? '已停止' : '停止请求已发送', true);
3023
- }
3024
- } catch (error) { setStatus(`停止失败:${error.message}`); }
3025
- finally { stoppingThreads.delete(threadId); updateComposerMode(); }
3026
- }
3027
- el('interruptTurn').onclick = interruptCurrentTurn;
3028
- function renderDraftImages() {
3029
- el('draftImages').replaceChildren();
3030
- for (const image of state.draftImages) {
3031
- const button = document.createElement('button');
3032
- button.type = 'button'; button.className = 'image-thumb'; button.title = '移除图片';
3033
- button.setAttribute('aria-label', '移除图片');
3034
- const preview = document.createElement('img');
3035
- preview.src = authorizedAssetUrl(image.thumbnailUrl); preview.alt = '待发送图片';
3036
- const remove = document.createElement('span'); remove.className = 'remove-image'; remove.textContent = '×';
3037
- button.append(preview, remove);
3038
- button.onclick = () => {
3039
- if (state.sending) return;
3040
- state.draftImages = state.draftImages.filter(item => item.id !== image.id);
3041
- transport.removeImage(image.id);
3042
- renderDraftImages(); updatePromptActions();
3043
- };
3044
- el('draftImages').append(button);
3045
- }
3046
- }
3047
- async function compressUploadImage(file) {
3048
- let bitmap;
3049
- try { bitmap = await createImageBitmap(file); }
3050
- catch { throw new Error('无法读取这张图片,请选择 JPG、PNG 或 WebP 图片'); }
3051
- const canvas = document.createElement('canvas');
3052
- try {
3053
- const scale = Math.min(1, 2560 / Math.max(bitmap.width, bitmap.height));
3054
- canvas.width = Math.max(1, Math.round(bitmap.width * scale));
3055
- canvas.height = Math.max(1, Math.round(bitmap.height * scale));
3056
- const context = canvas.getContext('2d');
3057
- if (!context) throw new Error('无法处理图片');
3058
- context.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
3059
- const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/webp', 0.88));
3060
- if (!blob || blob.size > 16 * 1024 * 1024) throw new Error('图片压缩失败,请换一张图片重试');
3061
- return blob;
3062
- } finally {
3063
- bitmap.close(); canvas.width = 0; canvas.height = 0;
3064
- }
3065
- }
3066
- el('addMenuTrigger').onclick = event => {
3067
- event.stopPropagation();
3068
- if (el('addMenuTrigger').disabled) return;
3069
- const open = el('addMenu').hidden;
3070
- closeModelMenu(); closePermissionMenu(); closeContextDetails();
3071
- el('addMenu').hidden = !open;
3072
- el('addMenuTrigger').setAttribute('aria-expanded', String(open));
3073
- };
3074
- el('addImage').onclick = () => { closeComposerAddMenu(); el('imagePicker').click(); };
3075
- el('imagePicker').onchange = async () => {
3076
- const files = Array.from(el('imagePicker').files || []);
3077
- el('imagePicker').value = '';
3078
- if (!files.length || state.uploadingImages || state.sending) return;
3079
- if (files.length + state.draftImages.length > 4) { showActionToast('每条消息最多 4 张图片'); return; }
3080
- state.uploadingImages = true; setSendControlsDisabled(true);
3081
- showActionToast('正在添加图片…');
3082
- try {
3083
- for (const file of files) {
3084
- const compressed = await compressUploadImage(file);
3085
- const result = await transport.addImage(compressed);
3086
- if (!state.draftImages.some(image => image.id === result.id)) state.draftImages.push(result);
3087
- renderDraftImages();
3088
- }
3089
- } catch (error) { showActionToast(error.message); }
3090
- finally { state.uploadingImages = false; setSendControlsDisabled(false); updatePromptActions(); }
3091
- };
3092
- el('prompt').addEventListener('input', () => {
3093
- scheduleDraftSave();
3094
- updatePromptActions();
3095
- // Native editing keeps a middle caret visible; appended text (including voice)
3096
- // also needs scrolling after the textarea's layout has updated.
3097
- const prompt = el('prompt');
3098
- if (prompt.selectionEnd === prompt.value.length) requestAnimationFrame(() => {
3099
- if (prompt.selectionEnd === prompt.value.length) prompt.scrollTop = prompt.scrollHeight;
3100
- });
3101
- });
3102
- el('clearPendingSettings').onclick = event => {
3103
- event.stopPropagation();
3104
- if (!state.selectedId) return;
3105
- state.pendingSettingsByThread.delete(state.selectedId);
3106
- resetModelMenuToCurrent();
3107
- };
3108
- el('timeline').addEventListener('scroll', () => {
3109
- state.timelineAtBottom = syncTimelinePosition();
3110
- updateScrollToBottomButton();
3111
- maybeAutoLoadHistory();
3112
- }, { passive: true });
3113
- const timelineResizeObserver = new ResizeObserver(() => {
3114
- state.timelineAtBottom = syncTimelinePosition();
3115
- updateScrollToBottomButton();
3116
- maybeAutoLoadHistory();
3117
- });
3118
- timelineResizeObserver.observe(el('timeline'));
3119
- el('scrollToBottom').onclick = () => {
3120
- scrollTimelineToBottom(true);
3121
- };
3122
- localStorage.removeItem('codexRemoteModelSelection');
3123
- localStorage.removeItem('codexRemoteModel');
3124
- localStorage.removeItem('codexRemoteThinking');
3125
- el('modelTrigger').onclick = async event => {
3126
- event.stopPropagation();
3127
- closeContextDetails();
3128
- const willOpen = el('modelMenu').hidden;
3129
- if (!willOpen) {
3130
- closeModelMenu();
3131
- return;
3132
- }
3133
- if (connectionBlocked() || state.historyLoading) return;
3134
- const threadId = state.selectedId;
3135
- const menuRequest = ++modelMenuRequest;
3136
- const isCurrent = () => state.selectedId === threadId && menuRequest === modelMenuRequest && !connectionBlocked();
3137
- const immediate = transport.has('immediateSettings');
3138
- const menu = el('modelMenu');
3139
- el('modelView').querySelector('.menu-title').textContent = immediate ? '设置此对话的模型' : '选择下一条消息使用的模型';
3140
- for (const hint of menu.querySelectorAll('.menu-hint')) {
3141
- hint.hidden = immediate;
3142
- hint.textContent = '设置将在发送下一条消息时生效';
3143
- }
3144
- resetModelMenuToCurrent();
3145
- showEffortView();
3146
- menu.hidden = false;
3147
- menu.inert = immediate;
3148
- menu.setAttribute('data-loading', String(immediate));
3149
- menu.setAttribute('aria-busy', String(immediate));
3150
- el('modelTrigger').setAttribute('aria-expanded', 'true');
3151
- try {
3152
- await Promise.all([
3153
- immediate ? loadServerModels(isCurrent) : Promise.resolve(),
3154
- threadId ? threadContext.update(threadId) : Promise.resolve(),
3155
- ]);
3156
- if (!isCurrent()) return;
3157
- if (immediate) resetModelMenuToCurrent();
3158
- } catch (error) {
3159
- if (!isCurrent()) return;
3160
- closeModelMenu();
3161
- setStatus(`读取模型失败:${error.message}`);
3162
- } finally {
3163
- if (menuRequest === modelMenuRequest) {
3164
- menu.inert = false;
3165
- menu.removeAttribute('data-loading');
3166
- menu.removeAttribute('aria-busy');
3167
- }
3168
- }
3169
- };
3170
-
3171
- function selectModel(button) {
3172
- if (!button || applyingServerSettings || connectionBlocked()) return;
3173
- el('model').value = button.dataset.model;
3174
- updateThinkingOptions(false);
3175
- updatePendingSettingsFromMenu();
3176
- if (transport.has('immediateSettings')) applyServerSettings();
3177
- }
3178
-
3179
- function selectThinking(button) {
3180
- if (!button || button.disabled || applyingServerSettings || connectionBlocked()) return;
3181
- state.menuThinking = button.dataset.thinking;
3182
- el('thinking').value = state.menuThinking;
3183
- updatePendingSettingsFromMenu();
3184
- if (transport.has('immediateSettings') && effortPointer === null) applyServerSettings();
3185
- }
3186
-
3187
- let effortPointer = null;
3188
- function selectEffortAt(clientX) {
3189
- const bounds = el('effortTrack').querySelector('.effort-options').getBoundingClientRect();
3190
- const buttons = [...document.querySelectorAll('.effort-option')];
3191
- const position = Math.min(1, Math.max(0, (clientX - bounds.left) / Math.max(1, bounds.width))) * (buttons.length - 1);
3192
- const enabled = buttons.map((button, index) => ({ button, distance: Math.abs(index - position) }))
3193
- .filter(item => !item.button.disabled).sort((a, b) => a.distance - b.distance);
3194
- selectThinking(enabled[0]?.button);
3195
- }
3196
- el('effortTrack').addEventListener('pointerdown', event => {
3197
- if (event.button !== 0 || effortPointer !== null || applyingServerSettings) return;
3198
- event.preventDefault();
3199
- effortPointer = event.pointerId;
3200
- el('effortTrack').setPointerCapture(event.pointerId);
3201
- selectEffortAt(event.clientX);
3202
- });
3203
- el('effortTrack').addEventListener('pointermove', event => {
3204
- if (event.pointerId === effortPointer) selectEffortAt(event.clientX);
3205
- });
3206
- el('effortTrack').addEventListener('pointerup', event => {
3207
- if (event.pointerId !== effortPointer) return;
3208
- selectEffortAt(event.clientX);
3209
- effortPointer = null;
3210
- el('effortTrack').releasePointerCapture(event.pointerId);
3211
- if (transport.has('immediateSettings')) applyServerSettings();
3212
- });
3213
- el('effortTrack').addEventListener('lostpointercapture', () => { effortPointer = null; });
3214
- el('effortTrack').addEventListener('pointercancel', () => { effortPointer = null; });
3215
-
3216
- el('modelMenu').addEventListener('click', event => {
3217
- const target = event.target instanceof Element ? event.target : event.target.parentElement;
3218
- if (target?.closest('#fastMode')) {
3219
- event.preventDefault(); event.stopPropagation();
3220
- if (!el('fastMode').disabled) applyServerSettings(['priority', 'fast'].includes(state.currentServiceTier) ? null : fastServiceTier());
3221
- return;
3222
- }
3223
- const modelButton = target?.closest('.model-option');
3224
- if (modelButton) {
3225
- event.preventDefault();
3226
- event.stopPropagation();
3227
- selectModel(modelButton);
3228
- return;
3229
- }
3230
- if (target?.closest('#effortModelButton')) {
3231
- event.preventDefault();
3232
- event.stopPropagation();
3233
- showModelView();
3234
- return;
3235
- }
3236
- if (target?.closest('#openEffort')) {
3237
- event.preventDefault();
3238
- event.stopPropagation();
3239
- showEffortView();
3240
- return;
3241
- }
3242
- const track = target?.closest('#effortTrack');
3243
- if (track) {
3244
- event.preventDefault();
3245
- event.stopPropagation();
3246
- // Pointer interaction is handled above; keyboard activation still selects a stop.
3247
- if (event.detail === 0) selectThinking(target.closest('.effort-option'));
3248
- }
3249
- }, true);
3250
- function closeContextDetails() {
3251
- el('contextDetails').hidden = true;
3252
- el('contextTrigger').setAttribute('aria-expanded', 'false');
3253
- }
3254
- el('contextTrigger').onclick = event => {
3255
- event.stopPropagation();
3256
- const open = el('contextDetails').hidden;
3257
- closeModelMenu(); closePermissionMenu();
3258
- el('contextDetails').textContent = el('contextMeter').title;
3259
- el('contextDetails').hidden = !open;
3260
- el('contextTrigger').setAttribute('aria-expanded', String(open));
3261
- };
3262
- document.addEventListener('click', event => {
3263
- if (!event.target.closest('.model-control')) closeModelMenu();
3264
- if (!event.target.closest('.permission-control')) closePermissionMenu();
3265
- if (!event.target.closest('.context-control')) closeContextDetails();
3266
- if (!event.target.closest('.composer-add')) closeComposerAddMenu();
3267
- });
3268
- document.addEventListener('keydown', event => {
3269
- if (event.key === 'Escape') { closeModelMenu(); closePermissionMenu(); closeContextDetails(); closeComposerAddMenu(); }
3270
- });
3271
- resetModelMenuToCurrent();
3272
- el('imageViewerClose').onclick = () => el('imageViewer').close();
3273
- el('imageViewerOriginal').onclick = () => void ensureViewerOriginal().catch(error => showActionToast('原图读取失败:' + error.message, 3000));
3274
- el('imageViewerDownload').onclick = () => void downloadViewerImage();
3275
- el('imageViewer').onclick = event => {
3276
- if (event.target === el('imageViewer')) el('imageViewer').close();
3277
- };
3278
- el('imageViewer').addEventListener('close', () => {
3279
- el('imageViewerImage').removeAttribute('src');
3280
- imageViewerOriginalLoader = null; imageViewerOriginalUrl = '';
3281
- });
3282
- function taskListLocked() {
3283
- return platform.embedded && !state.selectedThread && !isNewThread();
3284
- }
3285
-
3286
- function toggleTaskList() {
3287
- if (document.body.classList.contains('sidebar-open')) {
3288
- if (!taskListLocked()) document.body.classList.remove('sidebar-open');
3289
- } else {
3290
- showTaskList();
3291
- }
3292
- }
3293
-
3294
- function handleNativeBack() {
3295
- const dialogs = [...document.querySelectorAll('dialog[open]')];
3296
- const dialog = dialogs.at(-1);
3297
- if (dialog) {
3298
- const closeAllowed = dialog.dispatchEvent(new Event('cancel', { cancelable: true }));
3299
- if (closeAllowed && dialog.open) dialog.close();
3300
- return true;
3301
- }
3302
- const addMenu = el('addMenu');
3303
- const overlayOpen = !el('voiceStopPopover').hidden
3304
- || !el('modelMenu').hidden
3305
- || !el('permissionMenu').hidden
3306
- || !el('contextDetails').hidden
3307
- || addMenu?.hidden === false;
3308
- if (overlayOpen) {
3309
- el('voiceStopPopover').hidden = true;
3310
- closeModelMenu(); closePermissionMenu(); closeContextDetails();
3311
- if (addMenu) {
3312
- addMenu.hidden = true;
3313
- el('addMenuTrigger')?.setAttribute('aria-expanded', 'false');
3314
- }
3315
- return true;
3316
- }
3317
- if (document.body.classList.contains('sidebar-open')) {
3318
- if (!taskListLocked()) document.body.classList.remove('sidebar-open');
3319
- return true;
3320
- }
3321
- if (communityView?.isOpen()) {
3322
- communityView.close();
3323
- return true;
3324
- }
3325
- if (platform.voice.active) return true;
3326
- if (matchMedia('(max-width:599px)').matches) {
3327
- showTaskList();
3328
- return true;
3329
- }
3330
- if (document.body.classList.contains('sidebar-collapsed')) {
3331
- document.body.classList.remove('sidebar-collapsed');
3332
- }
3333
- return true;
3334
- }
3335
- window.harmonyHandleBack = handleNativeBack;
3336
- window.harmonyToggleTaskList = toggleTaskList;
3337
- window.addEventListener('beforeunload', () => {
3338
- saveActiveDraft();
3339
- timelineResizeObserver.disconnect();
3340
- usage.close();
3341
- closeEventStream();
3342
- threadContext.stop();
3343
- stopThreadPolling();
3344
- });
3345
- setInterval(() => timelineRenderer.updateDurations(), 1000);
3346
- if (platform.embedded) {
3347
- el('terminalSidebarToggle').onclick = toggleTaskList;
3348
- el('wideSidebarToggle').onclick = () => {
3349
- if (taskListLocked()) return;
3350
- if (matchMedia('(max-width:599px)').matches) {
3351
- document.body.classList.remove('sidebar-open');
3352
- return;
3353
- }
3354
- document.body.classList.add('sidebar-collapsed');
3355
- el('wideSidebarExpand').focus();
3356
- };
3357
- el('wideSidebarExpand').onclick = () => {
3358
- document.body.classList.remove('sidebar-collapsed');
3359
- void refreshThreadStates('wide-sidebar');
3360
- el('wideSidebarToggle').focus();
3361
- };
3362
- el('sidebarBackdrop').onclick = () => {
3363
- if (!taskListLocked()) document.body.classList.remove('sidebar-open');
3364
- };
3365
- let listWasVisible = taskListVisible();
3366
- const refreshVisibleList = () => {
3367
- if (taskListLocked() && (!document.body.classList.contains('sidebar-open')
3368
- || document.body.classList.contains('sidebar-collapsed'))) showTaskList();
3369
- const visible = taskListVisible();
3370
- if (visible && !listWasVisible) void refreshThreadStates('layout-visible');
3371
- listWasVisible = visible;
3372
- };
3373
- new MutationObserver(refreshVisibleList).observe(document.body, { attributes: true, attributeFilter: ['class'] });
3374
- window.addEventListener('resize', refreshVisibleList);
3375
- document.addEventListener('visibilitychange', refreshVisibleList);
3376
- refreshVisibleList();
3377
- renderConnectionLabel();
3378
- communityView = window.createCommunityView({
3379
- button: el('communityButton'),
3380
- page: el('communityPage'),
3381
- onOpen: () => {
3382
- document.body.classList.remove('sidebar-open');
3383
- el('terminalChatTitle').textContent = 'Code Relax 社区';
3384
- },
3385
- onClose: () => {
3386
- el('terminalChatTitle').textContent = isNewThread() ? '新对话' : (el('selectedTitle').textContent || 'Code Relax');
3387
- },
3388
- });
3389
- el('usageButton').onclick = () => usage.open();
3390
- const updateAnnouncement = value => {
3391
- const button = el('announcementButton');
3392
- const title = value && typeof value === 'object' && typeof value.title === 'string' ? value.title.trim() : '';
3393
- el('announcementTitle').textContent = title;
3394
- button.hidden = !title;
3395
- button.setAttribute('aria-label', title ? `系统公告:${title}` : '查看最新公告');
3396
- };
3397
- platform.callbacks.announcementChanged = updateAnnouncement;
3398
- el('announcementButton').onclick = () => {
3399
- if (platform.has('openAnnouncement')) platform.openAnnouncement();
3400
- };
3401
- el('usageDialogClose').onclick = () => el('usageDialog').close();
3402
- for (const id of ['usageDialog']) {
3403
- const dialog = el(id);
3404
- let outside = false;
3405
- const isOutside = event => {
3406
- const rect = dialog.getBoundingClientRect();
3407
- return event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom;
3408
- };
3409
- dialog.addEventListener('pointerdown', event => { outside = event.target === dialog && isOutside(event); });
3410
- dialog.addEventListener('click', event => {
3411
- if (outside && event.target === dialog && isOutside(event)) dialog.close();
3412
- outside = false;
3413
- });
3414
- }
3415
- if (!window.codexSelfRelayConfig && platform.has('externalUiState') && platform.has('setExternalUi')) {
3416
- const toggle = el('externalUiToggle');
3417
- el('externalUiOption').hidden = false;
3418
- el('externalUiOption').dataset.activePage = /^https?:$/.test(location.protocol) ? 'debug' : 'builtin';
3419
- toggle.checked = platform.externalUiState();
3420
- platform.callbacks.externalUiChanged = (enabled, error) => {
3421
- toggle.checked = enabled; toggle.disabled = false;
3422
- showActionToast(error ? `页面切换失败:${error}` : '页面来源已切换');
3423
- };
3424
- toggle.onchange = () => {
3425
- toggle.disabled = true;
3426
- try { platform.setExternalUi(toggle.checked, state.selectedId || '', pageSnapshot()); }
3427
- catch (error) { toggle.checked = !toggle.checked; toggle.disabled = false; showActionToast(error.message); }
3428
- };
3429
- }
3430
- const openConnectionSettings = () => {
3431
- if (platform.voice.active) return;
3432
- if (platform.has('returnToComputer')) { platform.returnToComputer(); return; }
3433
- if (!platform.has('openSettings')) { setStatus('请更新鸿蒙客户端后使用连接设置', false); return; }
3434
- platform.openSettings();
3435
- };
3436
- const connectionSettings = el('connectionSettings');
3437
- connectionSettings.setAttribute('aria-label', transport.has('managedConnection') ? '返回工作站连接方式' : `修改连接 ${location.host}`);
3438
- connectionSettings.onclick = openConnectionSettings;
3439
- const reloadPage = el('reloadDebugPage');
3440
- reloadPage.hidden = !/^https?:$/.test(location.protocol);
3441
- reloadPage.onclick = openLatestDebugPage;
3442
- const recoveryButton = el('recoverShared');
3443
- const networkDiagnostics = el('networkDiagnostics');
3444
- const viewAppLog = el('viewAppLog');
3445
- const sendAppLog = el('sendAppLog');
3446
- const clearAppLog = el('clearAppLog');
3447
- const installedAppVersion = el('installedAppVersion');
3448
- if (globalThis.codexNative?.installedAppVersion) {
3449
- const value = globalThis.codexNative.installedAppVersion();
3450
- installedAppVersion.textContent = value || '';
3451
- installedAppVersion.title = installedAppVersion.textContent;
3452
- installedAppVersion.hidden = !value;
3453
- }
3454
- networkDiagnostics.hidden = !platform.has('openNetworkDiagnostics');
3455
- networkDiagnostics.onclick = () => {
3456
- el('debugMenu').open = false;
3457
- if (platform.has('openNetworkDiagnostics')) platform.openNetworkDiagnostics();
3458
- };
3459
- viewAppLog.hidden = !platform.has('openAppLog');
3460
- viewAppLog.onclick = () => {
3461
- el('debugMenu').open = false;
3462
- if (platform.has('openAppLog')) platform.openAppLog();
3463
- };
3464
- const recoveryResult = el('sharedRecoveryResult');
3465
- const recoveryExecute = el('sharedRecoveryExecute');
3466
- let recoveryOperationId;
3467
- let recoveryEvents;
3468
- const showRecoveryResult = value => { recoveryResult.hidden = false; recoveryResult.textContent = value; };
3469
- recoveryButton.onclick = () => {
3470
- if (globalThis.codexNative?.openComputerMode) { globalThis.codexNative.openComputerMode(); return; }
3471
- if (!transport.recoverySupported()) { showRecoveryResult('需要新版 Bridge,并使用局域网或 Code Relax 中继;也可进入手机工作站详情启用完整控制。'); return; }
3472
- if (!recoveryEvents) {
3473
- recoveryEvents = transport.recoveryEvents();
3474
- recoveryEvents.addEventListener('progress', event => {
3475
- const result = JSON.parse(event.data);
3476
- if (result.status === 'none') return;
3477
- const names = { pending: '待执行', running: '执行中', completed: '已完成', failed: '失败' };
3478
- recoveryResult.style.whiteSpace = 'pre-line';
3479
- showRecoveryResult([...(result.steps || []).map(step => `${step.label}:${names[step.status] || step.status}`), result.message, result.error].filter(Boolean).join('\n'));
3480
- const busy = ['scheduled', 'running'].includes(result.status);
3481
- recoveryExecute.disabled = busy; recoveryButton.disabled = busy;
3482
- });
3483
- recoveryEvents.addEventListener('error', () => showRecoveryResult('管理连接中断;恢复连接后会自动同步进度,操作不会重发。'));
3484
- }
3485
- const bytes = crypto.getRandomValues(new Uint8Array(16));
3486
- bytes[6] = (bytes[6] & 15) | 64; bytes[8] = (bytes[8] & 63) | 128;
3487
- const hex = [...bytes].map(value => value.toString(16).padStart(2, '0')).join('');
3488
- recoveryOperationId = `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
3489
- el('sharedRecoveryConfirm').hidden = false;
3490
- };
3491
- el('sharedRecoveryCancel').onclick = () => { el('sharedRecoveryConfirm').hidden = true; };
3492
- const retryReadOnlyThread = () => {
3493
- if (!threadReadOnly() || !state.selectedThread) return;
3494
- const thread = state.selectedThread;
3495
- state.timeline = null;
3496
- void selectThread(thread);
3497
- };
3498
- el('sharedServiceNotice').onclick = () => {
3499
- if (el('sharedServiceNotice').dataset.action === 'retry-thread') retryReadOnlyThread();
3500
- else if (globalThis.codexNative?.openComputerStatus) globalThis.codexNative.openComputerStatus();
3501
- else recoveryButton.onclick();
3502
- };
3503
- el('sharedServiceNotice').onkeydown = event => {
3504
- if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); el('sharedServiceNotice').onclick(); }
3505
- };
3506
- recoveryExecute.onclick = async () => {
3507
- recoveryExecute.disabled = true; recoveryButton.disabled = true;
3508
- try {
3509
- const result = await transport.recoverShared(recoveryOperationId);
3510
- el('sharedRecoveryConfirm').hidden = true;
3511
- showRecoveryResult(`${result.message} 操作编号:${result.operationId}`);
3512
- } catch (error) {
3513
- el('sharedRecoveryConfirm').hidden = true;
3514
- showRecoveryResult(`提交结果未确认:${error.message}。连接恢复后自动同步进度,不要重复提交。`);
3515
- }
3516
- };
3517
- const updateDebugAccess = enabled => {
3518
- const allowed = enabled === true;
3519
- el('debugMenu').hidden = !allowed;
3520
- sendAppLog.hidden = !(allowed && platform.has('sendAppLog'));
3521
- clearAppLog.hidden = !(allowed && platform.has('clearAppLog'));
3522
- if (!allowed) { el('debugMenu').open = false; closeComposerAddMenu(); }
3523
- };
3524
- platform.callbacks.debugAccessChanged = updateDebugAccess;
3525
- updateDebugAccess(platform.has('debugAccessState') && platform.debugAccessState());
3526
- document.addEventListener('click', event => {
3527
- const menu = el('debugMenu');
3528
- if (menu.open && !menu.contains(event.target)) menu.open = false;
3529
- });
3530
- sendAppLog.onclick = () => {
3531
- if (!platform.has('sendAppLog')) return;
3532
- closeComposerAddMenu();
3533
- platform.sendAppLog();
3534
- };
3535
- platform.callbacks.appLogFeedback = message => showActionToast(message, 3000);
3536
- clearAppLog.onclick = () => {
3537
- if (!platform.has('clearAppLog')) return;
3538
- closeComposerAddMenu(); platform.clearAppLog();
3539
- };
3540
- const returnToComputer = el('returnToComputer');
3541
- returnToComputer.hidden = !platform.has('returnToComputer');
3542
- returnToComputer.onclick = () => { if (platform.has('returnToComputer')) platform.returnToComputer(); };
3543
- el('voice').disabled = !platform.has('toggleVoice');
3544
- el('voice').title = '语音输入';
3545
- el('voice').setAttribute('aria-label', '语音输入');
3546
- el('voice').onclick = () => {
3547
- if (!platform.has('toggleVoice')) {
3548
- showActionToast('请更新鸿蒙客户端后使用语音输入');
3549
- return;
3550
- }
3551
- if (platform.voice.stopping) return;
3552
- if (platform.voice.active) platform.voice.stopping = true;
3553
- platform.toggleVoice();
3554
- };
3555
- platform.voice.active = false;
3556
- platform.voice.stopping = false;
3557
- let pendingVoiceSend = null;
3558
- let pendingVoiceEdit = false;
3559
- let voicePrefix = '';
3560
- const hideVoiceStop = () => { el('voiceStopPopover').hidden = true; };
3561
- const showVoiceStop = event => {
3562
- const popover = el('voiceStopPopover');
3563
- const composer = popover.closest('.composer');
3564
- popover.hidden = false;
3565
- if (!composer) return;
3566
- const hostRect = composer.getBoundingClientRect();
3567
- const popoverRect = popover.getBoundingClientRect();
3568
- const edge = 4, gap = 8;
3569
- const desiredLeft = event.clientX - hostRect.left - popoverRect.width / 2;
3570
- const maxLeft = Math.max(edge, hostRect.width - popoverRect.width - edge);
3571
- popover.style.left = `${Math.max(edge, Math.min(desiredLeft, maxLeft))}px`;
3572
- popover.style.top = `${Math.max(8 - hostRect.top, event.clientY - hostRect.top - popoverRect.height - gap)}px`;
3573
- };
3574
- el('prompt').addEventListener('pointerdown', event => {
3575
- if (platform.voice.active) event.preventDefault();
3576
- });
3577
- el('prompt').addEventListener('click', event => {
3578
- if (!platform.voice.active || platform.voice.stopping) return;
3579
- el('stopVoiceToEdit').disabled = false;
3580
- el('stopVoiceToEdit').textContent = '停止录音';
3581
- showVoiceStop(event);
3582
- });
3583
- document.addEventListener('pointerdown', event => {
3584
- if (event.target === el('prompt') || el('voiceStopPopover').contains(event.target)) return;
3585
- hideVoiceStop(); pendingVoiceEdit = false;
3586
- });
3587
- document.addEventListener('keydown', event => { if (event.key === 'Escape') hideVoiceStop(); });
3588
- el('stopVoiceToEdit').onclick = () => {
3589
- if (!platform.voice.active || platform.voice.stopping) return;
3590
- pendingVoiceSend = null; pendingVoiceEdit = true;
3591
- platform.voice.stopping = true;
3592
- el('stopVoiceToEdit').disabled = true;
3593
- el('stopVoiceToEdit').textContent = '正在结束录音…';
3594
- updateComposerMode();
3595
- platform.toggleVoice();
3596
- };
3597
- platform.callbacks.beginVoice = () => {
3598
- if ((!state.selectedId && !isNewThread()) || state.sending || state.uploadingImages) return false;
3599
- pendingVoiceSend = null;
3600
- pendingVoiceEdit = false; hideVoiceStop();
3601
- platform.voice.stopping = false;
3602
- voicePrefix = el('prompt').value;
3603
- platform.voice.active = true;
3604
- el('voice').setAttribute('aria-pressed', 'true');
3605
- el('voice').title = '结束录音';
3606
- el('prompt').readOnly = true;
3607
- setSendControlsDisabled(true);
3608
- el('send').disabled = false;
3609
- updateComposerMode();
3610
- return true;
3611
- };
3612
- platform.callbacks.sendVoice = mode => {
3613
- if (pendingVoiceSend !== null) return;
3614
- pendingVoiceEdit = false; hideVoiceStop();
3615
- pendingVoiceSend = mode;
3616
- el('send').disabled = true;
3617
- if (!platform.voice.stopping) {
3618
- platform.voice.stopping = true;
3619
- platform.toggleVoice();
3620
- }
3621
- };
3622
- platform.callbacks.voiceText = text => {
3623
- if (!platform.voice.active) return;
3624
- el('prompt').value = voicePrefix + text;
3625
- el('prompt').dispatchEvent(new Event('input', { bubbles:true }));
3626
- };
3627
- platform.callbacks.endVoice = (error, cancelled = false) => {
3628
- const mode = pendingVoiceSend;
3629
- const edit = pendingVoiceEdit && !cancelled;
3630
- pendingVoiceEdit = false; hideVoiceStop();
3631
- pendingVoiceSend = null;
3632
- platform.voice.stopping = false;
3633
- platform.voice.active = false;
3634
- el('voice').setAttribute('aria-pressed', 'false');
3635
- el('voice').title = '语音输入';
3636
- el('prompt').readOnly = false;
3637
- setSendControlsDisabled(false);
3638
- updateComposerMode();
3639
- if (edit) { el('prompt').focus(); el('prompt').setSelectionRange(el('prompt').value.length, el('prompt').value.length); }
3640
- if (error) showActionToast(error);
3641
- else if (!cancelled && mode !== null) sendMessage(mode);
3642
- };
3643
- platform.callbacks.pause = () => {
3644
- pendingVoiceEdit = false; hideVoiceStop();
3645
- stationConnection.suspend();
3646
- conversation.invalidate({ clearProgress: true });
3647
- setConversationConnection('disconnected');
3648
- history.cancelSave();
3649
- closeEventStream(); threadContext.stop(); stopThreadPolling();
3650
- transport.pause().catch(error => setStatus(error.message));
3651
- };
3652
- platform.callbacks.resetConnection = () => {
3653
- saveActiveDraft();
3654
- stationConnection.suspend();
3655
- setNewThreadState(false); state.newThreadProject = null; state.returnThread = null;
3656
- history.cancelSave(); conversation.clear({ resetSettings: true });
3657
- closeEventStream(); threadContext.stop(); stopThreadPolling(); closeModelMenu(); closePermissionMenu();
3658
- state.pendingSettingsByThread.clear(); state.draftImages = []; transport.clearImages();
3659
- state.threads = []; state.threadListComplete = false;
3660
- state.archivedThreads = []; state.showingArchived = false; state.projects = [];
3661
- el('prompt').value = ''; el('selected').hidden = true; el('empty').hidden = false;
3662
- showTaskList(); setConversationTitle('选择聊天');
3663
- };
3664
- let resumeWork;
3665
- async function resumeConversation(preserveConnection) {
3666
- if (preserveConnection && enteredConversation && state.connection === 'connected' && transport.connectionState() === 'connected') {
3667
- const selection = conversation.generation();
3668
- foregroundChecking = true;
3669
- stationConnection.suspend();
3670
- updateComposerMode();
3671
- try {
3672
- await transport.checkConnection();
3673
- if (!conversation.isCurrent(selection)) return;
3674
- if (state.connection === 'connected') return;
3675
- } catch (error) {
3676
- if (!conversation.isCurrent(selection)) return;
3677
- setStatus(error.message);
3678
- await transport.pause();
3679
- } finally {
3680
- stationConnection.activate();
3681
- foregroundChecking = false;
3682
- updateComposerMode();
3683
- }
3684
- }
3685
- stationConnection.activate();
3686
- if (!enteredConversation) { await enterConversation(); return; }
3687
- await stationConnection.recover();
3688
- }
3689
- platform.callbacks.resume = (preserveConnection = false) => {
3690
- if (!resumeWork) resumeWork = resumeConversation(preserveConnection).finally(() => { resumeWork = null; });
3691
- return resumeWork;
3692
- };
3693
- platform.callbacks.relayRecovering = text => {
3694
- setConversationConnection('connecting');
3695
- history.cancelSave(); conversation.invalidate({ historyLoading: true, clearProgress: true });
3696
- closeEventStream(); threadContext.stop(); stopThreadPolling();
3697
- updateComposerMode(); setStatus('');
3698
- };
3699
- platform.callbacks.relayRestore = restoreRelayConversation;
3700
- platform.callbacks.relayRecovered = () => { state.historyProgress = ''; setConversationConnection('connected'); startThreadPolling(); setStatus('已连接', true); };
3701
- platform.callbacks.relayRecoveryFailed = text => { state.historyProgress = ''; stopThreadPolling(); setConversationConnection('disconnected'); setStatus(text); };
3702
- }
3703
- function showConnectionProgress(step, title, detail) {
3704
- const entry = configureConnectionEntry();
3705
- const currentStep = Math.min(Math.max(step, 1), entry.steps.length);
3706
- el('entryStatus').textContent = title;
3707
- el('entryDetail').textContent = detail;
3708
- el('entryProgress').dataset.state = 'loading';
3709
- [...el('entrySteps').children].forEach((item, index) => {
3710
- item.dataset.state = index + 1 < currentStep ? 'done' : index + 1 === currentStep ? 'current' : 'pending';
3711
- if (index + 1 === currentStep) item.setAttribute('aria-current', 'step');
3712
- else item.removeAttribute('aria-current');
3713
- });
3714
- }
3715
- window.harmonyConnectionProgress = showConnectionProgress;
3716
- function connectionEntry() {
3717
- if (window.codexTransport) return { mode: 'official', label: 'OpenAI 中继', diagram: 'OpenAI 中继',
3718
- initialTitle: '正在检查 OpenAI 登录', initialDetail: '正在检查已保存的账号和设备授权。',
3719
- steps: ['确认 OpenAI 登录', '授权当前设备', '安全连接工作站', '载入工作站任务'] };
3720
- if (window.codexSelfRelayConfig) return { mode: 'private', label: 'Code Relax 中继', diagram: 'Code Relax 中继',
3721
- initialTitle: '正在连接 Code Relax 中继', initialDetail: '正在验证设备授权并联系工作站。',
3722
- steps: ['确认设备授权', '连接 Code Relax 中继', '联系工作站', '载入工作站任务'] };
3723
- return { mode: 'lan', label: '局域网', diagram: '同一局域网',
3724
- initialTitle: '正在检查局域网', initialDetail: '正在验证网络地址和设备授权。',
3725
- steps: ['检查局域网', '确认设备授权', '连接工作站', '载入工作站任务'] };
3726
- }
3727
- function configureConnectionEntry() {
3728
- const entry = connectionEntry();
3729
- el('entryMode').textContent = entry.label;
3730
- el('entryDiagram').dataset.mode = entry.mode;
3731
- el('entryDiagramMode').textContent = entry.diagram;
3732
- el('entryDiagram').setAttribute('aria-label', `移动端通过${entry.diagram}双向连接工作站`);
3733
- const steps = el('entrySteps');
3734
- if (steps.dataset.mode !== entry.mode) {
3735
- steps.replaceChildren(...entry.steps.map((label, index) => {
3736
- const item = document.createElement('li');
3737
- item.dataset.number = String(index + 1); item.textContent = label;
3738
- return item;
3739
- }));
3740
- steps.dataset.mode = entry.mode;
3741
- }
3742
- return entry;
3743
- }
3744
- function renderEntryConnectionFailure(error) {
3745
- const retryable = Boolean(window.codexSelfRelayConfig?.credential && !stationConnection.isPaused() && !isTerminalConnectionError(error));
3746
- if (retryable) showConnectionProgress(1, '正在恢复连接', error.message);
3747
- else {
3748
- el('entryProgress').dataset.state = 'error';
3749
- el('entryStatus').textContent = '连接未完成';
3750
- el('entryDetail').textContent = error.message;
3751
- el('entryRetry').hidden = false;
3752
- }
3753
- return retryable;
3754
- }
3755
- async function enterConversation() {
3756
- window.codexEntryStarted = true;
3757
- clearTimeout(entryRetryTimer); entryRetryTimer = undefined;
3758
- if (stationConnection.isPaused()) return;
3759
- if (entryWork) return entryWork;
3760
- if (pageResume) setConversationConnection('connecting', true);
3761
- const entry = connectionEntry();
3762
- showConnectionProgress(1, entry.initialTitle, entry.initialDetail);
3763
- el('entryRetry').hidden = true;
3764
- if (!pageResume || waitForInitialThreads) platform.reportEntry?.(true);
3765
- const entryEpoch = stationConnection.generation();
3766
- const entryCurrent = () => stationConnection.isCurrent(entryEpoch);
3767
- entryWork = (async () => {
3768
- try {
3769
- if (!state.selectedThread) await restoreStartupView().catch(cacheError);
3770
- const initialSelection = conversation.generation();
3771
- showConnectionProgress(3, '正在连接工作站', '正在建立连接并确认工作站状态。');
3772
- await api('/api/health');
3773
- if (!entryCurrent()) return;
3774
- showConnectionProgress(4, '正在载入工作站任务', '连接已建立,正在读取任务列表。');
3775
- // Official relay identity and LAN execution mode are authoritative after health.
3776
- if (!state.selectedThread && !isNewThread() && conversation.isCurrent(initialSelection)) {
3777
- await restoreStartupView().catch(cacheError);
3778
- if (!entryCurrent()) return;
3779
- }
3780
- const restoredThreadList = pageResume && state.threadListComplete;
3781
- const threadsLoaded = restoredThreadList || await loadThreads(true);
3782
- if (!entryCurrent()) return;
3783
- if (!threadsLoaded) return;
3784
- configureConnectionEntry();
3785
- setConversationConnection('connected');
3786
- enteredConversation = true; entryAttempts = 0;
3787
- document.body.classList.remove('connection-entry');
3788
- platform.reportEntry?.(false);
3789
- usage.start();
3790
- startThreadPolling();
3791
- if (platform.embedded) {
3792
- if (!conversation.isCurrent(initialSelection) || isNewThread()) return;
3793
- const visited = readLastVisitedThread();
3794
- const preferred = window.CodexPageResume.preferredThreadId({ pageResume, resumeConfig, visited });
3795
- const last = state.selectedId || preferred;
3796
- const thread = state.threads.find(item => item.id === last) || state.selectedThread;
3797
- if (thread) {
3798
- rememberVisitedThread(thread);
3799
- if (state.selectedId === thread.id && state.timeline) { state.selectedThread = thread; await restoreRelayConversation(); }
3800
- else await selectThread(thread);
3801
- } else {
3802
- conversation.clear();
3803
- el('selected').hidden = true; el('empty').hidden = false; showTaskList();
3804
- }
3805
- }
3806
- } catch (error) {
3807
- if (!entryCurrent()) return;
3808
- setConversationConnection('disconnected');
3809
- if (!waitForInitialThreads && (enteredConversation || state.timeline?.cached)) setStatus(error.message);
3810
- else {
3811
- document.body.classList.add('connection-entry');
3812
- platform.reportEntry?.(true);
3813
- if (renderEntryConnectionFailure(error)) {
3814
- entryRetryTimer = setTimeout(() => { entryRetryTimer = undefined; void enterConversation(); },
3815
- Math.min(1000 * 2 ** Math.min(entryAttempts++, 5), 30000) * (0.8 + Math.random() * 0.2));
3816
- }
3817
- }
3818
- } finally {
3819
- el('entryRetry').disabled = false;
3820
- entryWork = undefined;
3821
- }
3822
- })();
3823
- return entryWork;
3824
- }
3825
- el('firstChooseTask').onclick = showTaskList;
3826
- el('firstNewChat').onclick = () => el('newChat').click();
3827
- el('entryRetry').onclick = () => enterConversation();
3828
- el('entryBack').hidden = !platform.embedded;
3829
- el('entryBack').onclick = () => platform.openSettings();
3830
- void enterConversation();