@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.
- package/README.md +83 -71
- package/dist/bin/cleanup-accounts.mjs +63 -0
- package/dist/bin/codex-remote.mjs +323 -184
- package/dist/bin/manage-relay-admin.mjs +20 -0
- package/dist/bin/migrate-relay-db.mjs +109 -0
- package/dist/bin/publish-android-release.mjs +63 -0
- package/dist/bin/publish-harmony-release.mjs +46 -0
- package/dist/bin/release-upload-server.mjs +174 -0
- package/dist/bin/self-relay-admin.mjs +8 -0
- package/dist/bin/self-relay-demo.mjs +2 -2
- package/dist/bin/self-relay-server.mjs +43 -30
- package/dist/build-platform.json +1 -0
- package/dist/get-shared-service-url.ps1 +6 -6
- package/dist/shared/app-server-events.cjs +368 -238
- package/dist/shared/code-browser.cjs +159 -0
- package/dist/shared/file-changes.cjs +31 -0
- package/dist/shared/file-citations.cjs +117 -0
- package/dist/shared/file-mentions.cjs +92 -0
- package/dist/shared/message-delivery.cjs +100 -0
- package/dist/shared/p2p-data-channel.cjs +22 -0
- package/dist/shared/p2p-file-channel.cjs +84 -0
- package/dist/shared/plan-pricing.cjs +32 -0
- package/dist/shared/question-attention.cjs +69 -0
- package/dist/shared/server-requests.cjs +157 -5
- package/dist/shared/service-endpoints.cjs +21 -0
- package/dist/shared/timeline-sync.cjs +111 -5
- package/dist/skills/code-relax/SKILL.md +13 -8
- package/dist/src/archived-threads.mjs +22 -0
- package/dist/src/bounded-process-log.mjs +63 -0
- package/dist/src/celsius-client.mjs +107 -0
- package/dist/src/code-browser.mjs +100 -0
- package/dist/src/desktop-launcher.mjs +33 -38
- package/dist/src/dots-approval-timeline.mjs +99 -0
- package/dist/src/dots-avatar.mjs +42 -0
- package/dist/src/dots-chat-projection.mjs +50 -0
- package/dist/src/dots-chat-server.mjs +482 -0
- package/dist/src/dots-message-images.mjs +319 -0
- package/dist/src/dots-profile.mjs +77 -0
- package/dist/src/dots-room-activity.mjs +94 -0
- package/dist/src/dots-room-client.mjs +135 -0
- package/dist/src/dots-room-projection.mjs +94 -0
- package/dist/src/dots-schedules.mjs +66 -0
- package/dist/src/dots-summary-tasks.mjs +32 -0
- package/dist/src/dots-task-catalog.mjs +430 -0
- package/dist/src/dots-task-files.mjs +52 -0
- package/dist/src/file-transfer-range.mjs +22 -0
- package/dist/src/file-upload-session.mjs +130 -0
- package/dist/src/hosted-history-app-server.mjs +103 -0
- package/dist/src/hosted-history-cache.mjs +113 -0
- package/dist/src/lan-socket-server.mjs +241 -84
- package/dist/src/managed-app-server.mjs +54 -16
- package/dist/src/message-images.mjs +0 -18
- package/dist/src/message-upload-files.mjs +109 -0
- package/dist/src/onboarding.mjs +10 -29
- package/dist/src/plan-pricing.mjs +47 -0
- package/dist/src/platform/README.md +2 -2
- package/dist/src/platform/codex-protocol.mjs +46 -0
- package/dist/src/platform/codex-runtime.mjs +3 -0
- package/dist/src/platform/credential-store.mjs +3 -0
- package/dist/src/platform/desktop-host.mjs +3 -0
- package/dist/src/platform/desktop-project-registration.mjs +3 -0
- package/dist/src/platform/desktop-project-store.mjs +208 -0
- package/dist/src/platform/file-opener.mjs +9 -0
- package/dist/src/platform/linux/codex-runtime.mjs +56 -0
- package/dist/src/platform/linux/credential-store.mjs +28 -0
- package/dist/src/platform/linux/desktop-host.mjs +83 -0
- package/dist/src/platform/linux/desktop-installation.mjs +36 -0
- package/dist/src/platform/linux/desktop-project-registration.mjs +11 -0
- package/dist/src/platform/linux/network-info.mjs +11 -0
- package/dist/src/platform/linux/process.mjs +42 -0
- package/dist/src/platform/linux/project-locations.mjs +17 -0
- package/dist/src/platform/linux/resident-startup.mjs +52 -0
- package/dist/src/platform/linux/retained-runtime.mjs +76 -0
- package/dist/src/platform/linux/service-host.mjs +18 -0
- package/dist/src/platform/linux/system.mjs +49 -0
- package/dist/src/platform/linux/user-environment.mjs +7 -0
- package/dist/src/platform/macos/codex-runtime.mjs +125 -0
- package/dist/src/platform/macos/credential-store.mjs +10 -0
- package/dist/src/platform/macos/desktop-host.mjs +82 -0
- package/dist/src/platform/macos/desktop-project-registration.mjs +11 -0
- package/dist/src/platform/macos/network-info.mjs +8 -0
- package/dist/src/platform/macos/project-locations.mjs +8 -0
- package/dist/src/platform/macos/resident-startup.mjs +84 -0
- package/dist/src/platform/macos/retained-runtime.mjs +127 -0
- package/dist/src/platform/macos/service-host.mjs +18 -0
- package/dist/src/platform/macos/system.mjs +29 -0
- package/dist/src/platform/macos/user-environment.mjs +7 -0
- package/dist/src/platform/native-package.mjs +58 -0
- package/dist/src/platform/network-info.mjs +3 -0
- package/dist/src/platform/path-identity.mjs +5 -0
- package/dist/src/platform/posix/network-candidates.mjs +23 -0
- package/dist/src/platform/posix/runtime-paths.mjs +25 -0
- package/dist/src/platform/power-inhibitor.mjs +22 -0
- package/dist/src/platform/project-locations.mjs +3 -0
- package/dist/src/platform/runtime-paths.mjs +8 -0
- package/dist/src/platform/select.mjs +5 -0
- package/dist/src/platform/service-host.mjs +3 -0
- package/dist/src/platform/user-environment.mjs +3 -0
- package/dist/src/platform/windows/activate-desktop.ps1 +48 -0
- package/dist/src/platform/windows/codex-runtime.mjs +215 -0
- package/dist/src/platform/windows/credential-store.mjs +27 -27
- package/dist/src/platform/windows/credential-store.ps1 +49 -49
- package/dist/src/platform/windows/desktop-helper.mjs +179 -0
- package/dist/src/platform/windows/desktop-host.mjs +43 -23
- package/dist/src/platform/windows/desktop-info.ps1 +14 -16
- package/dist/src/platform/windows/desktop-project-registration.mjs +4 -0
- package/dist/src/platform/windows/desktop-runtime.ps1 +10 -0
- package/dist/src/platform/windows/desktop-shortcuts.ps1 +21 -28
- package/dist/src/platform/windows/dots-profile.mjs +49 -0
- package/dist/src/platform/windows/keep-awake.ps1 +20 -0
- package/dist/src/platform/windows/launch-worker.mjs +17 -18
- package/dist/src/platform/windows/project-locations.mjs +16 -0
- package/dist/src/platform/windows/resident-start.js +20 -0
- package/dist/src/platform/windows/resident-startup.ps1 +76 -20
- package/dist/src/platform/windows/rustdesk-video-probe.mjs +168 -0
- package/dist/src/platform/windows/service-host.mjs +24 -36
- package/dist/src/platform/windows/stop-desktop.ps1 +7 -4
- package/dist/src/platform/windows/user-environment.mjs +1 -1
- package/dist/src/project-create.mjs +121 -0
- package/dist/src/project-management.mjs +59 -0
- package/dist/src/project-remove.mjs +55 -0
- package/dist/src/project-update.mjs +59 -0
- package/dist/src/runtime-discovery.mjs +47 -0
- package/dist/src/self-relay/account-cleanup.mjs +30 -0
- package/dist/src/self-relay/account-connections.mjs +52 -0
- package/dist/src/self-relay/admin-cloud.mjs +101 -0
- package/dist/src/self-relay/admin-page.mjs +80 -0
- package/dist/src/self-relay/admin-server.mjs +74 -0
- package/dist/src/self-relay/admin-state.mjs +85 -19
- package/dist/src/self-relay/admin-style.mjs +14 -0
- package/dist/src/self-relay/android-releases.mjs +61 -0
- package/dist/src/self-relay/client.mjs +45 -45
- package/dist/src/self-relay/connector.mjs +421 -144
- package/dist/src/self-relay/demo.mjs +34 -35
- package/dist/src/self-relay/device-info.mjs +27 -0
- package/dist/src/self-relay/device-store.mjs +10 -2
- package/dist/src/self-relay/diagnostics.mjs +54 -0
- package/dist/src/self-relay/email-accounts.mjs +183 -0
- package/dist/src/self-relay/file-wire.mjs +13 -0
- package/dist/src/self-relay/lan-authorizations.mjs +44 -2
- package/dist/src/self-relay/lifecycle.mjs +250 -272
- package/dist/src/self-relay/long-pairing.mjs +40 -0
- package/dist/src/self-relay/mysql-state.mjs +118 -0
- package/dist/src/self-relay/p2p-probe.mjs +41 -5
- package/dist/src/self-relay/privacy-page.mjs +26 -0
- package/dist/src/self-relay/public-releases.mjs +52 -0
- package/dist/src/self-relay/rtc-file-bridge.mjs +41 -0
- package/dist/src/self-relay/server.mjs +652 -114
- package/dist/src/self-relay/usage-daily.mjs +55 -0
- package/dist/src/self-relay/usage-meter.mjs +141 -0
- package/dist/src/server.mjs +1519 -1793
- package/dist/src/service-control.mjs +17 -7
- package/dist/src/service-doctor.mjs +86 -33
- package/dist/src/service-health.mjs +2 -2
- package/dist/src/service-install.mjs +91 -85
- package/dist/src/service-lifecycle.mjs +117 -108
- package/dist/src/service-power.mjs +58 -0
- package/dist/src/service-restart.mjs +43 -10
- package/dist/src/service-update-worker.mjs +42 -0
- package/dist/src/service-update.mjs +71 -0
- package/dist/src/shared-app-server.mjs +561 -407
- package/dist/src/shared-catalog.mjs +95 -13
- package/dist/src/shared-recovery.mjs +15 -11
- package/dist/src/shared-thread-stream.mjs +147 -63
- package/dist/src/skill-install.mjs +0 -13
- package/dist/src/turn-model-history.mjs +47 -0
- package/dist/src/usage-reset.mjs +17 -0
- package/dist/src/workstation-downloads.mjs +120 -0
- package/package.json +35 -37
- package/tools/postinstall.mjs +86 -22
- package/dist/src/app-server-client.mjs +0 -468
- package/dist/src/app-server-tasks.mjs +0 -358
- package/dist/src/debug-ui.mjs +0 -131
- package/dist/src/platform/windows/desktop-monitor.mjs +0 -34
- package/dist/src/platform/windows/desktop-shortcut-run.ps1 +0 -5
- package/dist/src/platform/windows/desktop-tools.mjs +0 -48
- package/dist/src/thread-catalog.mjs +0 -47
- package/dist/tools/find-desktop-pipe.mjs +0 -10
- package/dist/web/activity-view.js +0 -283
- package/dist/web/approval-ui.js +0 -188
- package/dist/web/capabilities.js +0 -57
- package/dist/web/chat-transport.js +0 -202
- package/dist/web/chat.css +0 -782
- package/dist/web/chat.js +0 -3830
- package/dist/web/client-platform.js +0 -11
- package/dist/web/community-view.js +0 -20
- package/dist/web/community.css +0 -77
- package/dist/web/community.html +0 -37
- package/dist/web/composer-controller.js +0 -101
- package/dist/web/conversation-controller.js +0 -99
- package/dist/web/disclosure-state-controller.js +0 -95
- package/dist/web/draft-controller.js +0 -103
- package/dist/web/harmony-platform.js +0 -52
- package/dist/web/highlight-powershell.min.js +0 -39
- package/dist/web/highlight.min.js +0 -1262
- package/dist/web/history-cache.js +0 -240
- package/dist/web/history-controller.js +0 -167
- package/dist/web/index.html +0 -325
- package/dist/web/lan-session.js +0 -122
- package/dist/web/link-action-controller.js +0 -212
- package/dist/web/message-send-controller.js +0 -177
- package/dist/web/message-view.js +0 -98
- package/dist/web/p2p-data-channel.js +0 -50
- package/dist/web/p2p-diagnostics.js +0 -28
- package/dist/web/p2p-probe.js +0 -123
- package/dist/web/page-resume.js +0 -28
- package/dist/web/pending-message-store.js +0 -108
- package/dist/web/queue-controller.js +0 -82
- package/dist/web/resources.json +0 -1
- package/dist/web/self-relay-session.js +0 -129
- package/dist/web/server-requests.js +0 -67
- package/dist/web/station-connection-controller.js +0 -75
- package/dist/web/task-list-view.js +0 -296
- package/dist/web/thread-attention-controller.js +0 -124
- package/dist/web/thread-context-controller.js +0 -30
- package/dist/web/thread-list-controller.js +0 -61
- package/dist/web/thread-list-sync.js +0 -86
- package/dist/web/thread-title-controller.js +0 -57
- package/dist/web/timeline-formatters.js +0 -249
- package/dist/web/timeline-reducer.js +0 -81
- package/dist/web/timeline-renderer.js +0 -161
- package/dist/web/timeline-scroll-controller.js +0 -50
- package/dist/web/timeline-sync.js +0 -67
- package/dist/web/usage-controller.js +0 -258
- package/dist/web/vendor/README.md +0 -14
- package/dist/web/vendor/highlight-powershell.min.js +0 -39
- package/dist/web/vendor/highlight.LICENSE.txt +0 -29
- package/dist/web/vendor/highlight.min.js +0 -1262
- package/dist/web/vendor/lucide.LICENSE.txt +0 -17
- package/node_modules/@img/colour/LICENSE.md +0 -82
- package/node_modules/@img/colour/README.md +0 -15
- package/node_modules/@img/colour/color.cjs +0 -1596
- package/node_modules/@img/colour/index.cjs +0 -1
- package/node_modules/@img/colour/index.d.ts +0 -929
- package/node_modules/@img/colour/package.json +0 -58
- package/node_modules/@img/sharp-win32-x64/LICENSE +0 -191
- package/node_modules/@img/sharp-win32-x64/README.md +0 -59
- package/node_modules/@img/sharp-win32-x64/index.cjs +0 -1
- package/node_modules/@img/sharp-win32-x64/lib/libvips-42.dll +0 -0
- package/node_modules/@img/sharp-win32-x64/lib/libvips-cpp-8.18.6.dll +0 -0
- package/node_modules/@img/sharp-win32-x64/lib/sharp-win32-x64-0.35.4.node +0 -0
- package/node_modules/@img/sharp-win32-x64/package.json +0 -40
- package/node_modules/@img/sharp-win32-x64/versions.json +0 -30
- package/node_modules/@node-datachannel/win32-x64-msvc/node_datachannel.node +0 -0
- package/node_modules/@node-datachannel/win32-x64-msvc/package.json +0 -20
- package/node_modules/ansi-regex/index.d.ts +0 -37
- package/node_modules/ansi-regex/index.js +0 -10
- package/node_modules/ansi-regex/license +0 -9
- package/node_modules/ansi-regex/package.json +0 -55
- package/node_modules/ansi-regex/readme.md +0 -78
- package/node_modules/ansi-styles/index.d.ts +0 -345
- package/node_modules/ansi-styles/index.js +0 -163
- package/node_modules/ansi-styles/license +0 -9
- package/node_modules/ansi-styles/package.json +0 -56
- package/node_modules/ansi-styles/readme.md +0 -152
- package/node_modules/camelcase/index.d.ts +0 -63
- package/node_modules/camelcase/index.js +0 -76
- package/node_modules/camelcase/license +0 -9
- package/node_modules/camelcase/package.json +0 -43
- package/node_modules/camelcase/readme.md +0 -99
- package/node_modules/cliui/CHANGELOG.md +0 -76
- package/node_modules/cliui/LICENSE.txt +0 -14
- package/node_modules/cliui/README.md +0 -115
- package/node_modules/cliui/index.js +0 -354
- package/node_modules/cliui/package.json +0 -65
- package/node_modules/color-convert/CHANGELOG.md +0 -54
- package/node_modules/color-convert/LICENSE +0 -21
- package/node_modules/color-convert/README.md +0 -68
- package/node_modules/color-convert/conversions.js +0 -839
- package/node_modules/color-convert/index.js +0 -81
- package/node_modules/color-convert/package.json +0 -48
- package/node_modules/color-convert/route.js +0 -97
- package/node_modules/color-name/LICENSE +0 -8
- package/node_modules/color-name/README.md +0 -11
- package/node_modules/color-name/index.js +0 -152
- package/node_modules/color-name/package.json +0 -28
- package/node_modules/decamelize/index.js +0 -13
- package/node_modules/decamelize/license +0 -21
- package/node_modules/decamelize/package.json +0 -38
- package/node_modules/decamelize/readme.md +0 -48
- package/node_modules/detect-libc/LICENSE +0 -201
- package/node_modules/detect-libc/README.md +0 -163
- package/node_modules/detect-libc/index.d.ts +0 -14
- package/node_modules/detect-libc/lib/detect-libc.js +0 -313
- package/node_modules/detect-libc/lib/elf.js +0 -39
- package/node_modules/detect-libc/lib/filesystem.js +0 -51
- package/node_modules/detect-libc/lib/process.js +0 -24
- package/node_modules/detect-libc/package.json +0 -44
- package/node_modules/dijkstrajs/.travis.yml +0 -4
- package/node_modules/dijkstrajs/CONTRIBUTING.md +0 -8
- package/node_modules/dijkstrajs/LICENSE.md +0 -19
- package/node_modules/dijkstrajs/README.md +0 -18
- package/node_modules/dijkstrajs/dijkstra.js +0 -165
- package/node_modules/dijkstrajs/package.json +0 -60
- package/node_modules/dijkstrajs/test/dijkstra.test.js +0 -96
- package/node_modules/emoji-regex/LICENSE-MIT.txt +0 -20
- package/node_modules/emoji-regex/README.md +0 -73
- package/node_modules/emoji-regex/es2015/index.js +0 -6
- package/node_modules/emoji-regex/es2015/text.js +0 -6
- package/node_modules/emoji-regex/index.d.ts +0 -23
- package/node_modules/emoji-regex/index.js +0 -6
- package/node_modules/emoji-regex/package.json +0 -50
- package/node_modules/emoji-regex/text.js +0 -6
- package/node_modules/find-up/index.d.ts +0 -137
- package/node_modules/find-up/index.js +0 -89
- package/node_modules/find-up/license +0 -9
- package/node_modules/find-up/package.json +0 -53
- package/node_modules/find-up/readme.md +0 -156
- package/node_modules/get-caller-file/LICENSE.md +0 -6
- package/node_modules/get-caller-file/README.md +0 -41
- package/node_modules/get-caller-file/index.d.ts +0 -2
- package/node_modules/get-caller-file/index.js +0 -22
- package/node_modules/get-caller-file/index.js.map +0 -1
- package/node_modules/get-caller-file/package.json +0 -42
- package/node_modules/is-fullwidth-code-point/index.d.ts +0 -17
- package/node_modules/is-fullwidth-code-point/index.js +0 -50
- package/node_modules/is-fullwidth-code-point/license +0 -9
- package/node_modules/is-fullwidth-code-point/package.json +0 -42
- package/node_modules/is-fullwidth-code-point/readme.md +0 -39
- package/node_modules/locate-path/index.d.ts +0 -83
- package/node_modules/locate-path/index.js +0 -65
- package/node_modules/locate-path/license +0 -9
- package/node_modules/locate-path/package.json +0 -45
- package/node_modules/locate-path/readme.md +0 -122
- package/node_modules/node-datachannel/.clang-format +0 -17
- package/node_modules/node-datachannel/.editorconfig +0 -12
- package/node_modules/node-datachannel/.eslintignore +0 -8
- package/node_modules/node-datachannel/.eslintrc.json +0 -27
- package/node_modules/node-datachannel/.gitmodules +0 -3
- package/node_modules/node-datachannel/.prettierignore +0 -7
- package/node_modules/node-datachannel/.prettierrc +0 -13
- package/node_modules/node-datachannel/API.md +0 -247
- package/node_modules/node-datachannel/BULDING.md +0 -26
- package/node_modules/node-datachannel/CMakeLists.txt +0 -153
- package/node_modules/node-datachannel/LICENSE +0 -373
- package/node_modules/node-datachannel/README.md +0 -129
- package/node_modules/node-datachannel/dist/cjs/index.cjs +0 -11
- package/node_modules/node-datachannel/dist/cjs/index.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/cjs/lib/datachannel-stream.cjs +0 -101
- package/node_modules/node-datachannel/dist/cjs/lib/datachannel-stream.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/cjs/lib/index.cjs +0 -91
- package/node_modules/node-datachannel/dist/cjs/lib/index.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/cjs/lib/node-datachannel.cjs +0 -94
- package/node_modules/node-datachannel/dist/cjs/lib/node-datachannel.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/cjs/lib/websocket-server.cjs +0 -45
- package/node_modules/node-datachannel/dist/cjs/lib/websocket-server.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/cjs/lib/websocket.cjs +0 -8
- package/node_modules/node-datachannel/dist/cjs/lib/websocket.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/cjs/polyfill/Events.cjs +0 -86
- package/node_modules/node-datachannel/dist/cjs/polyfill/Events.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/cjs/polyfill/Exception.cjs +0 -34
- package/node_modules/node-datachannel/dist/cjs/polyfill/Exception.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCCertificate.cjs +0 -34
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCCertificate.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDataChannel.cjs +0 -214
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDataChannel.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDtlsTransport.cjs +0 -53
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCDtlsTransport.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCError.cjs +0 -83
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCError.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceCandidate.cjs +0 -132
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceCandidate.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceTransport.cjs +0 -94
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCIceTransport.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCPeerConnection.cjs +0 -434
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCPeerConnection.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSctpTransport.cjs +0 -59
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSctpTransport.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSessionDescription.cjs +0 -45
- package/node_modules/node-datachannel/dist/cjs/polyfill/RTCSessionDescription.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/cjs/polyfill/index.cjs +0 -42
- package/node_modules/node-datachannel/dist/cjs/polyfill/index.cjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/index.mjs +0 -8
- package/node_modules/node-datachannel/dist/esm/index.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/lib/datachannel-stream.mjs +0 -78
- package/node_modules/node-datachannel/dist/esm/lib/datachannel-stream.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/lib/index.mjs +0 -64
- package/node_modules/node-datachannel/dist/esm/lib/index.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/lib/node-datachannel.mjs +0 -78
- package/node_modules/node-datachannel/dist/esm/lib/node-datachannel.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/lib/websocket-server.mjs +0 -43
- package/node_modules/node-datachannel/dist/esm/lib/websocket-server.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/lib/websocket.mjs +0 -6
- package/node_modules/node-datachannel/dist/esm/lib/websocket.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/polyfill/Events.mjs +0 -82
- package/node_modules/node-datachannel/dist/esm/polyfill/Events.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/polyfill/Exception.mjs +0 -28
- package/node_modules/node-datachannel/dist/esm/polyfill/Exception.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCCertificate.mjs +0 -30
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCCertificate.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCDataChannel.mjs +0 -210
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCDataChannel.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCDtlsTransport.mjs +0 -49
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCDtlsTransport.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCError.mjs +0 -79
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCError.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceCandidate.mjs +0 -128
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceCandidate.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceTransport.mjs +0 -89
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCIceTransport.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCPeerConnection.mjs +0 -430
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCPeerConnection.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCSctpTransport.mjs +0 -55
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCSctpTransport.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCSessionDescription.mjs +0 -41
- package/node_modules/node-datachannel/dist/esm/polyfill/RTCSessionDescription.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/esm/polyfill/index.mjs +0 -27
- package/node_modules/node-datachannel/dist/esm/polyfill/index.mjs.map +0 -1
- package/node_modules/node-datachannel/dist/types/lib/datachannel-stream.d.ts +0 -24
- package/node_modules/node-datachannel/dist/types/lib/index.d.ts +0 -241
- package/node_modules/node-datachannel/dist/types/lib/types.d.ts +0 -119
- package/node_modules/node-datachannel/dist/types/lib/websocket-server.d.ts +0 -13
- package/node_modules/node-datachannel/dist/types/lib/websocket.d.ts +0 -25
- package/node_modules/node-datachannel/dist/types/polyfill/Events.d.ts +0 -15
- package/node_modules/node-datachannel/dist/types/polyfill/RTCCertificate.d.ts +0 -9
- package/node_modules/node-datachannel/dist/types/polyfill/RTCDataChannel.d.ts +0 -29
- package/node_modules/node-datachannel/dist/types/polyfill/RTCDtlsTransport.d.ts +0 -15
- package/node_modules/node-datachannel/dist/types/polyfill/RTCError.d.ts +0 -17
- package/node_modules/node-datachannel/dist/types/polyfill/RTCIceCandidate.d.ts +0 -21
- package/node_modules/node-datachannel/dist/types/polyfill/RTCIceTransport.d.ts +0 -30
- package/node_modules/node-datachannel/dist/types/polyfill/RTCPeerConnection.d.ts +0 -64
- package/node_modules/node-datachannel/dist/types/polyfill/RTCSctpTransport.d.ts +0 -15
- package/node_modules/node-datachannel/dist/types/polyfill/RTCSessionDescription.d.ts +0 -10
- package/node_modules/node-datachannel/dist/types/polyfill/index.d.ts +0 -26
- package/node_modules/node-datachannel/jest.config.ts +0 -14
- package/node_modules/node-datachannel/package.json +0 -133
- package/node_modules/node-datachannel/rollup.config.mjs +0 -67
- package/node_modules/node-datachannel/src/cpp/data-channel-wrapper.cpp +0 -530
- package/node_modules/node-datachannel/src/cpp/data-channel-wrapper.h +0 -63
- package/node_modules/node-datachannel/src/cpp/ice-udp-mux-listener-wrapper.cpp +0 -172
- package/node_modules/node-datachannel/src/cpp/ice-udp-mux-listener-wrapper.h +0 -43
- package/node_modules/node-datachannel/src/cpp/main.cpp +0 -58
- package/node_modules/node-datachannel/src/cpp/media-audio-wrapper.cpp +0 -457
- package/node_modules/node-datachannel/src/cpp/media-audio-wrapper.h +0 -52
- package/node_modules/node-datachannel/src/cpp/media-av1packetization.cpp +0 -24
- package/node_modules/node-datachannel/src/cpp/media-av1packetization.h +0 -11
- package/node_modules/node-datachannel/src/cpp/media-av1rtppacketizer-wrapper.cpp +0 -126
- package/node_modules/node-datachannel/src/cpp/media-av1rtppacketizer-wrapper.h +0 -29
- package/node_modules/node-datachannel/src/cpp/media-direction.cpp +0 -43
- package/node_modules/node-datachannel/src/cpp/media-direction.h +0 -10
- package/node_modules/node-datachannel/src/cpp/media-h264rtppacketizer-wrapper.cpp +0 -126
- package/node_modules/node-datachannel/src/cpp/media-h264rtppacketizer-wrapper.h +0 -30
- package/node_modules/node-datachannel/src/cpp/media-h265rtppacketizer-wrapper.cpp +0 -126
- package/node_modules/node-datachannel/src/cpp/media-h265rtppacketizer-wrapper.h +0 -30
- package/node_modules/node-datachannel/src/cpp/media-h26xseparator.cpp +0 -32
- package/node_modules/node-datachannel/src/cpp/media-h26xseparator.h +0 -11
- package/node_modules/node-datachannel/src/cpp/media-mediahandler-helper.cpp +0 -31
- package/node_modules/node-datachannel/src/cpp/media-mediahandler-helper.h +0 -11
- package/node_modules/node-datachannel/src/cpp/media-pacinghandler-wrapper.cpp +0 -79
- package/node_modules/node-datachannel/src/cpp/media-pacinghandler-wrapper.h +0 -28
- package/node_modules/node-datachannel/src/cpp/media-rtcpnackresponder-wrapper.cpp +0 -68
- package/node_modules/node-datachannel/src/cpp/media-rtcpnackresponder-wrapper.h +0 -28
- package/node_modules/node-datachannel/src/cpp/media-rtcpreceivingsession-wrapper.cpp +0 -57
- package/node_modules/node-datachannel/src/cpp/media-rtcpreceivingsession-wrapper.h +0 -28
- package/node_modules/node-datachannel/src/cpp/media-rtcpsrreporter-wrapper.cpp +0 -93
- package/node_modules/node-datachannel/src/cpp/media-rtcpsrreporter-wrapper.h +0 -30
- package/node_modules/node-datachannel/src/cpp/media-rtppacketizationconfig-wrapper.cpp +0 -167
- package/node_modules/node-datachannel/src/cpp/media-rtppacketizationconfig-wrapper.h +0 -35
- package/node_modules/node-datachannel/src/cpp/media-rtppacketizer-wrapper.cpp +0 -95
- package/node_modules/node-datachannel/src/cpp/media-rtppacketizer-wrapper.h +0 -30
- package/node_modules/node-datachannel/src/cpp/media-track-wrapper.cpp +0 -458
- package/node_modules/node-datachannel/src/cpp/media-track-wrapper.h +0 -61
- package/node_modules/node-datachannel/src/cpp/media-video-wrapper.cpp +0 -526
- package/node_modules/node-datachannel/src/cpp/media-video-wrapper.h +0 -56
- package/node_modules/node-datachannel/src/cpp/peer-connection-wrapper.cpp +0 -1298
- package/node_modules/node-datachannel/src/cpp/peer-connection-wrapper.h +0 -89
- package/node_modules/node-datachannel/src/cpp/rtc-wrapper.cpp +0 -205
- package/node_modules/node-datachannel/src/cpp/rtc-wrapper.h +0 -24
- package/node_modules/node-datachannel/src/cpp/thread-safe-callback.cpp +0 -57
- package/node_modules/node-datachannel/src/cpp/thread-safe-callback.h +0 -47
- package/node_modules/node-datachannel/src/cpp/web-socket-server-wrapper.cpp +0 -275
- package/node_modules/node-datachannel/src/cpp/web-socket-server-wrapper.h +0 -41
- package/node_modules/node-datachannel/src/cpp/web-socket-wrapper.cpp +0 -796
- package/node_modules/node-datachannel/src/cpp/web-socket-wrapper.h +0 -63
- package/node_modules/node-datachannel/src/index.ts +0 -9
- package/node_modules/node-datachannel/src/lib/datachannel-stream.ts +0 -100
- package/node_modules/node-datachannel/src/lib/index.ts +0 -290
- package/node_modules/node-datachannel/src/lib/node-datachannel.ts +0 -84
- package/node_modules/node-datachannel/src/lib/types.ts +0 -169
- package/node_modules/node-datachannel/src/lib/websocket-server.ts +0 -37
- package/node_modules/node-datachannel/src/lib/websocket.ts +0 -26
- package/node_modules/node-datachannel/src/polyfill/Events.ts +0 -82
- package/node_modules/node-datachannel/src/polyfill/Exception.ts +0 -37
- package/node_modules/node-datachannel/src/polyfill/README.md +0 -41
- package/node_modules/node-datachannel/src/polyfill/RTCCertificate.ts +0 -21
- package/node_modules/node-datachannel/src/polyfill/RTCDataChannel.ts +0 -225
- package/node_modules/node-datachannel/src/polyfill/RTCDtlsTransport.ts +0 -46
- package/node_modules/node-datachannel/src/polyfill/RTCError.ts +0 -78
- package/node_modules/node-datachannel/src/polyfill/RTCIceCandidate.ts +0 -128
- package/node_modules/node-datachannel/src/polyfill/RTCIceTransport.ts +0 -90
- package/node_modules/node-datachannel/src/polyfill/RTCPeerConnection.ts +0 -527
- package/node_modules/node-datachannel/src/polyfill/RTCSctpTransport.ts +0 -51
- package/node_modules/node-datachannel/src/polyfill/RTCSessionDescription.ts +0 -41
- package/node_modules/node-datachannel/src/polyfill/index.ts +0 -38
- package/node_modules/node-datachannel/tsconfig.json +0 -21
- package/node_modules/p-limit/index.d.ts +0 -38
- package/node_modules/p-limit/index.js +0 -57
- package/node_modules/p-limit/license +0 -9
- package/node_modules/p-limit/package.json +0 -52
- package/node_modules/p-limit/readme.md +0 -101
- package/node_modules/p-locate/index.d.ts +0 -64
- package/node_modules/p-locate/index.js +0 -52
- package/node_modules/p-locate/license +0 -9
- package/node_modules/p-locate/package.json +0 -53
- package/node_modules/p-locate/readme.md +0 -90
- package/node_modules/p-try/index.d.ts +0 -39
- package/node_modules/p-try/index.js +0 -9
- package/node_modules/p-try/license +0 -9
- package/node_modules/p-try/package.json +0 -42
- package/node_modules/p-try/readme.md +0 -58
- package/node_modules/path-exists/index.d.ts +0 -28
- package/node_modules/path-exists/index.js +0 -23
- package/node_modules/path-exists/license +0 -9
- package/node_modules/path-exists/package.json +0 -39
- package/node_modules/path-exists/readme.md +0 -52
- package/node_modules/pngjs/.eslintignore +0 -2
- package/node_modules/pngjs/.eslintrc.json +0 -19
- package/node_modules/pngjs/.prettierignore +0 -3
- package/node_modules/pngjs/LICENSE +0 -20
- package/node_modules/pngjs/README.md +0 -429
- package/node_modules/pngjs/browser.js +0 -16096
- package/node_modules/pngjs/coverage/lcov-report/base.css +0 -354
- package/node_modules/pngjs/coverage/lcov-report/bitmapper.js.html +0 -881
- package/node_modules/pngjs/coverage/lcov-report/bitpacker.js.html +0 -554
- package/node_modules/pngjs/coverage/lcov-report/block-navigation.js +0 -77
- package/node_modules/pngjs/coverage/lcov-report/chunkstream.js.html +0 -647
- package/node_modules/pngjs/coverage/lcov-report/constants.js.html +0 -176
- package/node_modules/pngjs/coverage/lcov-report/crc.js.html +0 -200
- package/node_modules/pngjs/coverage/lcov-report/favicon.png +0 -0
- package/node_modules/pngjs/coverage/lcov-report/filter-pack.js.html +0 -593
- package/node_modules/pngjs/coverage/lcov-report/filter-parse-async.js.html +0 -152
- package/node_modules/pngjs/coverage/lcov-report/filter-parse-sync.js.html +0 -143
- package/node_modules/pngjs/coverage/lcov-report/filter-parse.js.html +0 -611
- package/node_modules/pngjs/coverage/lcov-report/format-normaliser.js.html +0 -359
- package/node_modules/pngjs/coverage/lcov-report/index.html +0 -426
- package/node_modules/pngjs/coverage/lcov-report/interlace.js.html +0 -365
- package/node_modules/pngjs/coverage/lcov-report/packer-async.js.html +0 -230
- package/node_modules/pngjs/coverage/lcov-report/packer-sync.js.html +0 -248
- package/node_modules/pngjs/coverage/lcov-report/packer.js.html +0 -467
- package/node_modules/pngjs/coverage/lcov-report/paeth-predictor.js.html +0 -128
- package/node_modules/pngjs/coverage/lcov-report/parser-async.js.html +0 -575
- package/node_modules/pngjs/coverage/lcov-report/parser-sync.js.html +0 -404
- package/node_modules/pngjs/coverage/lcov-report/parser.js.html +0 -950
- package/node_modules/pngjs/coverage/lcov-report/png-sync.js.html +0 -116
- package/node_modules/pngjs/coverage/lcov-report/png.js.html +0 -662
- package/node_modules/pngjs/coverage/lcov-report/prettify.css +0 -101
- package/node_modules/pngjs/coverage/lcov-report/prettify.js +0 -1007
- package/node_modules/pngjs/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/node_modules/pngjs/coverage/lcov-report/sorter.js +0 -169
- package/node_modules/pngjs/coverage/lcov-report/sync-inflate.js.html +0 -584
- package/node_modules/pngjs/coverage/lcov-report/sync-reader.js.html +0 -215
- package/node_modules/pngjs/coverage/lcov.info +0 -2177
- package/node_modules/pngjs/lib/bitmapper.js +0 -267
- package/node_modules/pngjs/lib/bitpacker.js +0 -158
- package/node_modules/pngjs/lib/chunkstream.js +0 -189
- package/node_modules/pngjs/lib/constants.js +0 -32
- package/node_modules/pngjs/lib/crc.js +0 -40
- package/node_modules/pngjs/lib/filter-pack.js +0 -171
- package/node_modules/pngjs/lib/filter-parse-async.js +0 -24
- package/node_modules/pngjs/lib/filter-parse-sync.js +0 -21
- package/node_modules/pngjs/lib/filter-parse.js +0 -177
- package/node_modules/pngjs/lib/format-normaliser.js +0 -93
- package/node_modules/pngjs/lib/interlace.js +0 -95
- package/node_modules/pngjs/lib/packer-async.js +0 -50
- package/node_modules/pngjs/lib/packer-sync.js +0 -56
- package/node_modules/pngjs/lib/packer.js +0 -129
- package/node_modules/pngjs/lib/paeth-predictor.js +0 -16
- package/node_modules/pngjs/lib/parser-async.js +0 -165
- package/node_modules/pngjs/lib/parser-sync.js +0 -108
- package/node_modules/pngjs/lib/parser.js +0 -290
- package/node_modules/pngjs/lib/png-sync.js +0 -12
- package/node_modules/pngjs/lib/png.js +0 -194
- package/node_modules/pngjs/lib/sync-inflate.js +0 -168
- package/node_modules/pngjs/lib/sync-reader.js +0 -45
- package/node_modules/pngjs/package.json +0 -73
- package/node_modules/qrcode/README.md +0 -770
- package/node_modules/qrcode/bin/qrcode +0 -159
- package/node_modules/qrcode/helper/to-sjis-browser.js +0 -2
- package/node_modules/qrcode/helper/to-sjis.js +0 -105
- package/node_modules/qrcode/lib/browser.js +0 -76
- package/node_modules/qrcode/lib/can-promise.js +0 -7
- package/node_modules/qrcode/lib/core/alignment-pattern.js +0 -83
- package/node_modules/qrcode/lib/core/alphanumeric-data.js +0 -59
- package/node_modules/qrcode/lib/core/bit-buffer.js +0 -37
- package/node_modules/qrcode/lib/core/bit-matrix.js +0 -65
- package/node_modules/qrcode/lib/core/byte-data.js +0 -30
- package/node_modules/qrcode/lib/core/error-correction-code.js +0 -135
- package/node_modules/qrcode/lib/core/error-correction-level.js +0 -50
- package/node_modules/qrcode/lib/core/finder-pattern.js +0 -22
- package/node_modules/qrcode/lib/core/format-info.js +0 -29
- package/node_modules/qrcode/lib/core/galois-field.js +0 -69
- package/node_modules/qrcode/lib/core/kanji-data.js +0 -54
- package/node_modules/qrcode/lib/core/mask-pattern.js +0 -234
- package/node_modules/qrcode/lib/core/mode.js +0 -167
- package/node_modules/qrcode/lib/core/numeric-data.js +0 -43
- package/node_modules/qrcode/lib/core/polynomial.js +0 -62
- package/node_modules/qrcode/lib/core/qrcode.js +0 -495
- package/node_modules/qrcode/lib/core/reed-solomon-encoder.js +0 -56
- package/node_modules/qrcode/lib/core/regex.js +0 -31
- package/node_modules/qrcode/lib/core/segments.js +0 -330
- package/node_modules/qrcode/lib/core/utils.js +0 -63
- package/node_modules/qrcode/lib/core/version-check.js +0 -9
- package/node_modules/qrcode/lib/core/version.js +0 -163
- package/node_modules/qrcode/lib/index.js +0 -12
- package/node_modules/qrcode/lib/renderer/canvas.js +0 -63
- package/node_modules/qrcode/lib/renderer/png.js +0 -78
- package/node_modules/qrcode/lib/renderer/svg-tag.js +0 -81
- package/node_modules/qrcode/lib/renderer/svg.js +0 -19
- package/node_modules/qrcode/lib/renderer/terminal/terminal-small.js +0 -85
- package/node_modules/qrcode/lib/renderer/terminal/terminal.js +0 -49
- package/node_modules/qrcode/lib/renderer/terminal.js +0 -9
- package/node_modules/qrcode/lib/renderer/utf8.js +0 -71
- package/node_modules/qrcode/lib/renderer/utils.js +0 -99
- package/node_modules/qrcode/lib/server.js +0 -138
- package/node_modules/qrcode/license +0 -10
- package/node_modules/qrcode/package.json +0 -77
- package/node_modules/require-directory/.jshintrc +0 -67
- package/node_modules/require-directory/.npmignore +0 -1
- package/node_modules/require-directory/.travis.yml +0 -3
- package/node_modules/require-directory/LICENSE +0 -22
- package/node_modules/require-directory/README.markdown +0 -184
- package/node_modules/require-directory/index.js +0 -86
- package/node_modules/require-directory/package.json +0 -40
- package/node_modules/require-main-filename/CHANGELOG.md +0 -26
- package/node_modules/require-main-filename/LICENSE.txt +0 -14
- package/node_modules/require-main-filename/README.md +0 -26
- package/node_modules/require-main-filename/index.js +0 -18
- package/node_modules/require-main-filename/package.json +0 -35
- package/node_modules/semver/LICENSE +0 -15
- package/node_modules/semver/README.md +0 -680
- package/node_modules/semver/bin/semver.js +0 -195
- package/node_modules/semver/classes/comparator.js +0 -143
- package/node_modules/semver/classes/index.js +0 -7
- package/node_modules/semver/classes/range.js +0 -577
- package/node_modules/semver/classes/semver.js +0 -350
- package/node_modules/semver/functions/clean.js +0 -8
- package/node_modules/semver/functions/cmp.js +0 -54
- package/node_modules/semver/functions/coerce.js +0 -62
- package/node_modules/semver/functions/compare-build.js +0 -9
- package/node_modules/semver/functions/compare-loose.js +0 -5
- package/node_modules/semver/functions/compare.js +0 -7
- package/node_modules/semver/functions/diff.js +0 -60
- package/node_modules/semver/functions/eq.js +0 -5
- package/node_modules/semver/functions/gt.js +0 -5
- package/node_modules/semver/functions/gte.js +0 -5
- package/node_modules/semver/functions/inc.js +0 -21
- package/node_modules/semver/functions/lt.js +0 -5
- package/node_modules/semver/functions/lte.js +0 -5
- package/node_modules/semver/functions/major.js +0 -5
- package/node_modules/semver/functions/minor.js +0 -5
- package/node_modules/semver/functions/neq.js +0 -5
- package/node_modules/semver/functions/parse.js +0 -18
- package/node_modules/semver/functions/patch.js +0 -5
- package/node_modules/semver/functions/prerelease.js +0 -8
- package/node_modules/semver/functions/rcompare.js +0 -5
- package/node_modules/semver/functions/rsort.js +0 -5
- package/node_modules/semver/functions/satisfies.js +0 -12
- package/node_modules/semver/functions/sort.js +0 -5
- package/node_modules/semver/functions/truncate.js +0 -48
- package/node_modules/semver/functions/valid.js +0 -8
- package/node_modules/semver/index.js +0 -93
- package/node_modules/semver/internal/constants.js +0 -37
- package/node_modules/semver/internal/debug.js +0 -11
- package/node_modules/semver/internal/identifiers.js +0 -29
- package/node_modules/semver/internal/lrucache.js +0 -42
- package/node_modules/semver/internal/parse-options.js +0 -17
- package/node_modules/semver/internal/re.js +0 -223
- package/node_modules/semver/package.json +0 -78
- package/node_modules/semver/preload.js +0 -4
- package/node_modules/semver/range.bnf +0 -17
- package/node_modules/semver/ranges/gtr.js +0 -6
- package/node_modules/semver/ranges/intersects.js +0 -9
- package/node_modules/semver/ranges/ltr.js +0 -6
- package/node_modules/semver/ranges/max-satisfying.js +0 -27
- package/node_modules/semver/ranges/min-satisfying.js +0 -26
- package/node_modules/semver/ranges/min-version.js +0 -63
- package/node_modules/semver/ranges/outside.js +0 -82
- package/node_modules/semver/ranges/simplify.js +0 -49
- package/node_modules/semver/ranges/subset.js +0 -249
- package/node_modules/semver/ranges/to-comparators.js +0 -10
- package/node_modules/semver/ranges/valid.js +0 -13
- package/node_modules/set-blocking/CHANGELOG.md +0 -26
- package/node_modules/set-blocking/LICENSE.txt +0 -14
- package/node_modules/set-blocking/README.md +0 -31
- package/node_modules/set-blocking/index.js +0 -7
- package/node_modules/set-blocking/package.json +0 -42
- package/node_modules/sharp/LICENSE +0 -191
- package/node_modules/sharp/README.md +0 -112
- package/node_modules/sharp/dist/channel.cjs +0 -177
- package/node_modules/sharp/dist/channel.mjs +0 -177
- package/node_modules/sharp/dist/colour.cjs +0 -199
- package/node_modules/sharp/dist/colour.mjs +0 -199
- package/node_modules/sharp/dist/composite.cjs +0 -213
- package/node_modules/sharp/dist/composite.mjs +0 -213
- package/node_modules/sharp/dist/constructor.cjs +0 -511
- package/node_modules/sharp/dist/constructor.mjs +0 -511
- package/node_modules/sharp/dist/index.cjs +0 -25
- package/node_modules/sharp/dist/index.d.cts +0 -1999
- package/node_modules/sharp/dist/index.d.mts +0 -2046
- package/node_modules/sharp/dist/index.mjs +0 -25
- package/node_modules/sharp/dist/input.cjs +0 -819
- package/node_modules/sharp/dist/input.mjs +0 -819
- package/node_modules/sharp/dist/is.cjs +0 -143
- package/node_modules/sharp/dist/is.mjs +0 -143
- package/node_modules/sharp/dist/libvips.cjs +0 -212
- package/node_modules/sharp/dist/libvips.mjs +0 -212
- package/node_modules/sharp/dist/operation.cjs +0 -1002
- package/node_modules/sharp/dist/operation.mjs +0 -1002
- package/node_modules/sharp/dist/output.cjs +0 -1785
- package/node_modules/sharp/dist/output.mjs +0 -1785
- package/node_modules/sharp/dist/resize.cjs +0 -617
- package/node_modules/sharp/dist/resize.mjs +0 -617
- package/node_modules/sharp/dist/sharp.cjs +0 -174
- package/node_modules/sharp/dist/sharp.mjs +0 -174
- package/node_modules/sharp/dist/utility.cjs +0 -301
- package/node_modules/sharp/dist/utility.mjs +0 -301
- package/node_modules/sharp/install/build.js +0 -38
- package/node_modules/sharp/lib/index.d.ts +0 -1999
- package/node_modules/sharp/package.json +0 -227
- package/node_modules/sharp/src/binding.gyp +0 -303
- package/node_modules/sharp/src/common.cc +0 -1186
- package/node_modules/sharp/src/common.h +0 -421
- package/node_modules/sharp/src/metadata.cc +0 -404
- package/node_modules/sharp/src/metadata.h +0 -95
- package/node_modules/sharp/src/operations.cc +0 -516
- package/node_modules/sharp/src/operations.h +0 -137
- package/node_modules/sharp/src/pipeline.cc +0 -1948
- package/node_modules/sharp/src/pipeline.h +0 -422
- package/node_modules/sharp/src/sharp.cc +0 -43
- package/node_modules/sharp/src/stats.cc +0 -186
- package/node_modules/sharp/src/stats.h +0 -62
- package/node_modules/sharp/src/utilities.cc +0 -289
- package/node_modules/sharp/src/utilities.h +0 -22
- package/node_modules/string-width/index.d.ts +0 -29
- package/node_modules/string-width/index.js +0 -47
- package/node_modules/string-width/license +0 -9
- package/node_modules/string-width/package.json +0 -56
- package/node_modules/string-width/readme.md +0 -50
- package/node_modules/strip-ansi/index.d.ts +0 -17
- package/node_modules/strip-ansi/index.js +0 -4
- package/node_modules/strip-ansi/license +0 -9
- package/node_modules/strip-ansi/package.json +0 -54
- package/node_modules/strip-ansi/readme.md +0 -46
- package/node_modules/which-module/LICENSE +0 -13
- package/node_modules/which-module/README.md +0 -58
- package/node_modules/which-module/index.js +0 -9
- package/node_modules/which-module/package.json +0 -41
- package/node_modules/wrap-ansi/index.js +0 -186
- package/node_modules/wrap-ansi/license +0 -9
- package/node_modules/wrap-ansi/package.json +0 -61
- package/node_modules/wrap-ansi/readme.md +0 -97
- package/node_modules/ws/LICENSE +0 -20
- package/node_modules/ws/README.md +0 -548
- package/node_modules/ws/browser.js +0 -8
- package/node_modules/ws/index.js +0 -22
- package/node_modules/ws/lib/buffer-util.js +0 -131
- package/node_modules/ws/lib/constants.js +0 -19
- package/node_modules/ws/lib/event-target.js +0 -292
- package/node_modules/ws/lib/extension.js +0 -203
- package/node_modules/ws/lib/limiter.js +0 -55
- package/node_modules/ws/lib/permessage-deflate.js +0 -530
- package/node_modules/ws/lib/receiver.js +0 -743
- package/node_modules/ws/lib/sender.js +0 -607
- package/node_modules/ws/lib/stream.js +0 -161
- package/node_modules/ws/lib/subprotocol.js +0 -62
- package/node_modules/ws/lib/validation.js +0 -152
- package/node_modules/ws/lib/websocket-server.js +0 -562
- package/node_modules/ws/lib/websocket.js +0 -1407
- package/node_modules/ws/package.json +0 -74
- package/node_modules/ws/wrapper.mjs +0 -21
- package/node_modules/y18n/CHANGELOG.md +0 -35
- package/node_modules/y18n/LICENSE +0 -13
- package/node_modules/y18n/README.md +0 -109
- package/node_modules/y18n/index.js +0 -188
- package/node_modules/y18n/package.json +0 -39
- package/node_modules/yargs/CHANGELOG.md +0 -420
- package/node_modules/yargs/LICENSE +0 -21
- package/node_modules/yargs/README.md +0 -140
- package/node_modules/yargs/build/lib/apply-extends.d.ts +0 -2
- package/node_modules/yargs/build/lib/apply-extends.js +0 -65
- package/node_modules/yargs/build/lib/argsert.d.ts +0 -2
- package/node_modules/yargs/build/lib/argsert.js +0 -65
- package/node_modules/yargs/build/lib/command.d.ts +0 -64
- package/node_modules/yargs/build/lib/command.js +0 -416
- package/node_modules/yargs/build/lib/common-types.d.ts +0 -36
- package/node_modules/yargs/build/lib/common-types.js +0 -25
- package/node_modules/yargs/build/lib/completion-templates.d.ts +0 -2
- package/node_modules/yargs/build/lib/completion-templates.js +0 -50
- package/node_modules/yargs/build/lib/completion.d.ts +0 -21
- package/node_modules/yargs/build/lib/completion.js +0 -135
- package/node_modules/yargs/build/lib/is-promise.d.ts +0 -1
- package/node_modules/yargs/build/lib/is-promise.js +0 -9
- package/node_modules/yargs/build/lib/levenshtein.d.ts +0 -1
- package/node_modules/yargs/build/lib/levenshtein.js +0 -58
- package/node_modules/yargs/build/lib/middleware.d.ts +0 -10
- package/node_modules/yargs/build/lib/middleware.js +0 -57
- package/node_modules/yargs/build/lib/obj-filter.d.ts +0 -1
- package/node_modules/yargs/build/lib/obj-filter.js +0 -14
- package/node_modules/yargs/build/lib/parse-command.d.ts +0 -11
- package/node_modules/yargs/build/lib/parse-command.js +0 -36
- package/node_modules/yargs/build/lib/process-argv.d.ts +0 -2
- package/node_modules/yargs/build/lib/process-argv.js +0 -31
- package/node_modules/yargs/build/lib/usage.d.ts +0 -49
- package/node_modules/yargs/build/lib/usage.js +0 -540
- package/node_modules/yargs/build/lib/validation.d.ts +0 -34
- package/node_modules/yargs/build/lib/validation.js +0 -330
- package/node_modules/yargs/build/lib/yargs.d.ts +0 -274
- package/node_modules/yargs/build/lib/yargs.js +0 -1190
- package/node_modules/yargs/build/lib/yerror.d.ts +0 -4
- package/node_modules/yargs/build/lib/yerror.js +0 -11
- package/node_modules/yargs/index.js +0 -40
- package/node_modules/yargs/locales/be.json +0 -46
- package/node_modules/yargs/locales/de.json +0 -46
- package/node_modules/yargs/locales/en.json +0 -51
- package/node_modules/yargs/locales/es.json +0 -46
- package/node_modules/yargs/locales/fi.json +0 -49
- package/node_modules/yargs/locales/fr.json +0 -53
- package/node_modules/yargs/locales/hi.json +0 -49
- package/node_modules/yargs/locales/hu.json +0 -46
- package/node_modules/yargs/locales/id.json +0 -50
- package/node_modules/yargs/locales/it.json +0 -46
- package/node_modules/yargs/locales/ja.json +0 -51
- package/node_modules/yargs/locales/ko.json +0 -49
- package/node_modules/yargs/locales/nb.json +0 -44
- package/node_modules/yargs/locales/nl.json +0 -49
- package/node_modules/yargs/locales/nn.json +0 -44
- package/node_modules/yargs/locales/pirate.json +0 -13
- package/node_modules/yargs/locales/pl.json +0 -49
- package/node_modules/yargs/locales/pt.json +0 -45
- package/node_modules/yargs/locales/pt_BR.json +0 -48
- package/node_modules/yargs/locales/ru.json +0 -46
- package/node_modules/yargs/locales/th.json +0 -46
- package/node_modules/yargs/locales/tr.json +0 -48
- package/node_modules/yargs/locales/zh_CN.json +0 -48
- package/node_modules/yargs/locales/zh_TW.json +0 -47
- package/node_modules/yargs/package.json +0 -92
- package/node_modules/yargs/yargs.js +0 -14
- package/node_modules/yargs-parser/CHANGELOG.md +0 -601
- package/node_modules/yargs-parser/LICENSE.txt +0 -14
- package/node_modules/yargs-parser/README.md +0 -449
- package/node_modules/yargs-parser/index.js +0 -1032
- package/node_modules/yargs-parser/lib/tokenize-arg-string.js +0 -40
- 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();
|