@songsid/agend 2.3.2 → 2.3.3-beta.10
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/dist/agent-cli-instructions.md +2 -2
- package/dist/agent-cli.js +13 -112
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +0 -1
- package/dist/agent-endpoint.js +1 -9
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +23 -5
- package/dist/agent-whats-new.js +58 -40
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +0 -7
- package/dist/auth-api.js +1 -15
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.js +1 -1
- package/dist/authority/authority-state.js.map +1 -1
- package/dist/authority/web-authority.d.ts +9 -4
- package/dist/authority/web-authority.js +10 -5
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/backend/antigravity.js +6 -1
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +20 -1
- package/dist/backend/claude-code.js +54 -29
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/claude-transcript-errors.d.ts +18 -0
- package/dist/backend/claude-transcript-errors.js +19 -0
- package/dist/backend/claude-transcript-errors.js.map +1 -0
- package/dist/backend/codex-session-lookup.d.ts +16 -3
- package/dist/backend/codex-session-lookup.js +45 -11
- package/dist/backend/codex-session-lookup.js.map +1 -1
- package/dist/backend/codex.d.ts +16 -0
- package/dist/backend/codex.js +184 -35
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.js +58 -12
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro-reconcile-store.js +8 -1
- package/dist/backend/kiro-reconcile-store.js.map +1 -1
- package/dist/backend/kiro.d.ts +0 -5
- package/dist/backend/kiro.js +2 -10
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.js +2 -1
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +53 -4
- package/dist/backend/types.js.map +1 -1
- package/dist/bundled-skills.d.ts +4 -6
- package/dist/bundled-skills.js +10 -14
- package/dist/bundled-skills.js.map +1 -1
- package/dist/channel/adapters/discord.js +60 -17
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-server.js +5 -5
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +1 -3
- package/dist/channel/mcp-tools.js +4 -58
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/classic-channel-manager.d.ts +4 -1
- package/dist/classic-channel-manager.js +21 -7
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +23 -6
- package/dist/cli.js.map +1 -1
- package/dist/config-history.d.ts +50 -0
- package/dist/config-history.js +263 -0
- package/dist/config-history.js.map +1 -0
- package/dist/config-validator.js +10 -8
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +11 -4
- package/dist/config.js.map +1 -1
- package/dist/cross-instance-envelope.d.ts +10 -5
- package/dist/cross-instance-envelope.js +20 -17
- package/dist/cross-instance-envelope.js.map +1 -1
- package/dist/daemon.d.ts +130 -64
- package/dist/daemon.js +603 -428
- package/dist/daemon.js.map +1 -1
- package/dist/daily-summary.d.ts +8 -1
- package/dist/daily-summary.js +8 -5
- package/dist/daily-summary.js.map +1 -1
- package/dist/dashboard-live.d.ts +1 -1
- package/dist/dashboard-live.js +3 -3
- package/dist/dashboard-live.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +1 -1
- package/dist/dashboard-permissions.js +1 -3
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/delivery-outbox.d.ts +31 -22
- package/dist/delivery-outbox.js +119 -161
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-outbox.test.js +10 -25
- package/dist/delivery-outbox.test.js.map +1 -1
- package/dist/doctor.d.ts +4 -0
- package/dist/doctor.js +16 -1
- package/dist/doctor.js.map +1 -1
- package/dist/fleet-level-config.js +1 -1
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +74 -66
- package/dist/fleet-manager.js +285 -414
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-process-handlers.d.ts +5 -0
- package/dist/fleet-process-handlers.js +21 -0
- package/dist/fleet-process-handlers.js.map +1 -1
- package/dist/fleet-service-owner.d.ts +11 -0
- package/dist/fleet-service-owner.js +3 -0
- package/dist/fleet-service-owner.js.map +1 -0
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +11 -1
- package/dist/general-knowledge/skills/delegation-playbook/SKILL.md +1 -1
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +1 -1
- package/dist/general-knowledge/skills/session-management/SKILL.md +7 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +13 -204
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +30 -68
- package/dist/instance-lifecycle.d.ts +19 -0
- package/dist/instance-lifecycle.js +73 -15
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.js +6 -7
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-reconcile.js +3 -3
- package/dist/kiro-reconcile.js.map +1 -1
- package/dist/locale.js +96 -58
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +1 -1
- package/dist/log-service.js.map +1 -1
- package/dist/needs-you-hub.d.ts +1 -0
- package/dist/needs-you-hub.js +6 -3
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +3 -1
- package/dist/needs-you-live.js +24 -2
- package/dist/needs-you-live.js.map +1 -1
- package/dist/needs-you.d.ts +13 -2
- package/dist/needs-you.js +11 -1
- package/dist/needs-you.js.map +1 -1
- package/dist/notify-socket.d.ts +10 -0
- package/dist/notify-socket.js +45 -0
- package/dist/notify-socket.js.map +1 -0
- package/dist/outbound-handlers.js +20 -8
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +0 -62
- package/dist/outbound-schemas.js +4 -43
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/parked-notices.d.ts +87 -0
- package/dist/parked-notices.js +194 -0
- package/dist/parked-notices.js.map +1 -0
- package/dist/public-link-progress.d.ts +5 -2
- package/dist/public-link-progress.js +4 -3
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-web-gateway.js +0 -9
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +12 -0
- package/dist/public-web-link.js +29 -5
- package/dist/public-web-link.js.map +1 -1
- package/dist/quota-notice.d.ts +23 -0
- package/dist/quota-notice.js +22 -0
- package/dist/quota-notice.js.map +1 -0
- package/dist/scheduler/task-board.d.ts +17 -25
- package/dist/scheduler/task-board.js +29 -39
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/sd-notify.d.ts +23 -15
- package/dist/sd-notify.js +88 -29
- package/dist/sd-notify.js.map +1 -1
- package/dist/search/search-docs.d.ts +1 -1
- package/dist/search/search-docs.js +2 -2
- package/dist/search/search-docs.js.map +1 -1
- package/dist/settings-api.d.ts +16 -1
- package/dist/settings-api.js +56 -16
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +3 -1
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +17 -3
- package/dist/settings-confirmation.js +8 -5
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-effect.js +0 -2
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +31 -1
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/setup-wizard.d.ts +3 -0
- package/dist/setup-wizard.js +4 -2
- package/dist/setup-wizard.js.map +1 -1
- package/dist/task-board-waker.d.ts +43 -1
- package/dist/task-board-waker.js +121 -9
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tmux-control.d.ts +1 -1
- package/dist/tmux-control.js +3 -2
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-manager.js +18 -16
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tmux-read.d.ts +2 -0
- package/dist/tmux-read.js +7 -1
- package/dist/tmux-read.js.map +1 -1
- package/dist/tool-permissions.js +3 -10
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +29 -7
- package/dist/topic-commands.js +97 -37
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.d.ts +7 -0
- package/dist/transcript-monitor.js +48 -3
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +5 -0
- package/dist/tunnel/cloudflared.js +32 -1
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/named-diagnostics.d.ts +2 -1
- package/dist/tunnel/named-diagnostics.js +4 -1
- package/dist/tunnel/named-diagnostics.js.map +1 -1
- package/dist/tunnel/named-link-test.d.ts +90 -0
- package/dist/tunnel/named-link-test.js +270 -0
- package/dist/tunnel/named-link-test.js.map +1 -0
- package/dist/tunnel/purpose-lane.d.ts +3 -1
- package/dist/tunnel/purpose-lane.js +2 -0
- package/dist/tunnel/purpose-lane.js.map +1 -1
- package/dist/types.d.ts +9 -11
- package/dist/ui/chat-ambient.js +4 -4
- package/dist/ui/chat-commands.js +3 -2
- package/dist/ui/chat-store.js +4 -1
- package/dist/ui/chat-strings.js +12 -46
- package/dist/ui/chat-thread.js +51 -59
- package/dist/ui/fleet-office-skin.js +37 -14
- package/dist/ui/fleet-office.js +31 -7
- package/dist/ui/html-attachment.js +22 -8
- package/dist/ui/panel-chat.js +21 -50
- package/dist/ui/panel-log.js +2 -2
- package/dist/ui/panel-needs.js +11 -9
- package/dist/ui/panel-settings.js +5 -3
- package/dist/ui/preview-panel.js +12 -21
- package/dist/ui/preview.js +10 -3
- package/dist/ui/search-dialog.js +30 -14
- package/dist/ui/settings-appearance.js +3 -37
- package/dist/ui/settings-confirm.js +18 -0
- package/dist/ui/settings-guests.js +13 -4
- package/dist/ui/settings-learn.js +1 -1
- package/dist/ui/settings-mods.js +5 -12
- package/dist/ui/settings-named-test.js +144 -0
- package/dist/ui/settings-registry.js +0 -2
- package/dist/ui/settings-strings.js +16 -16
- package/dist/ui/settings-web-authority.js +4 -3
- package/dist/ui/shared/app-i18n.js +15 -4
- package/dist/ui/shared/app-needs.js +5 -1
- package/dist/ui/shared/app-prefs.js +17 -17
- package/dist/ui/shared/app-shell.js +47 -9
- package/dist/ui/shared/app-store.js +6 -2
- package/dist/ui/shared/app.css +33 -39
- package/dist/ui/shared/app.js +5 -4
- package/dist/ui/shared/instance-nav.js +9 -9
- package/dist/ui/shared/instance-switch.js +8 -2
- package/dist/ui/shared/panel-view.js +22 -8
- package/dist/ui/shared/view-strings.js +6 -0
- package/dist/ui/shared/web-authorize.js +35 -13
- package/dist/update-check.d.ts +13 -4
- package/dist/update-check.js +17 -4
- package/dist/update-check.js.map +1 -1
- package/dist/usage/usage-api.d.ts +3 -0
- package/dist/usage/usage-api.js +5 -1
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/view-api.d.ts +2 -0
- package/dist/view-api.js +64 -0
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +9 -17
- package/dist/web-api.js +257 -371
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +0 -2
- package/dist/web-appearance-tools.js +2 -5
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.d.ts +0 -6
- package/dist/web-appearance-view.js +0 -1
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +14 -7
- package/dist/web-appearance.js +5 -6
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-asset-parts.d.ts +1 -17
- package/dist/web-asset-parts.js +1 -15
- package/dist/web-asset-parts.js.map +1 -1
- package/dist/web-auth.js +1 -7
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +3 -7
- package/dist/web-chat-history.js +10 -17
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +9 -1
- package/dist/web-commands.js +13 -0
- package/dist/web-commands.js.map +1 -1
- package/dist/web-experimental.d.ts +3 -8
- package/dist/web-experimental.js +9 -33
- package/dist/web-experimental.js.map +1 -1
- package/dist/web-guest-access.d.ts +40 -2
- package/dist/web-guest-access.js +50 -14
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-mod-bundle.d.ts +2 -45
- package/dist/web-mod-bundle.js +15 -63
- package/dist/web-mod-bundle.js.map +1 -1
- package/dist/web-mod-store.d.ts +3 -48
- package/dist/web-mod-store.js +30 -97
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-prefs.d.ts +13 -0
- package/dist/web-prefs.js +38 -0
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +6 -13
- package/dist/web-preview.js +12 -23
- package/dist/web-preview.js.map +1 -1
- package/dist/web-session.d.ts +11 -3
- package/dist/web-session.js +22 -11
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +1 -1
- package/dist/web-settings-patch.js +9 -7
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +0 -13
- package/dist/web-settings-tools.js +12 -84
- package/dist/web-settings-tools.js.map +1 -1
- package/dist/workflow-templates/default.md +2 -2
- package/dist/working-recovery.d.ts +0 -2
- package/dist/working-recovery.js +2 -23
- package/dist/working-recovery.js.map +1 -1
- package/launcher/agend-install.cjs +7 -1
- package/launcher/fleet-service-owner.cjs +150 -0
- package/package.json +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +0 -376
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +0 -673
- package/dist/task-board-summary.d.ts +0 -101
- package/dist/task-board-summary.js +0 -177
- package/dist/task-board-summary.js.map +0 -1
- package/dist/ui/app-effects.js +0 -251
- package/dist/ui/chat-effect-posters.js +0 -42
- package/dist/ui/chat-effects.js +0 -167
- package/dist/ui/effect-trial-controller.js +0 -124
- package/dist/ui/effect-trial-offer.js +0 -2
- package/dist/ui/effect-trial-panel.js +0 -40
- package/dist/ui/effects-waker.js +0 -2
- package/dist/ui/settings-effects.js +0 -202
- package/dist/ui/settings-owner-effects.js +0 -82
- package/dist/ui/shared/effect-app-runtime.js +0 -299
- package/dist/ui/shared/effect-baseline.js +0 -64
- package/dist/ui/shared/effect-chat-runtime.js +0 -162
- package/dist/ui/shared/effect-cursor.js +0 -126
- package/dist/ui/shared/effect-display-metadata.js +0 -39
- package/dist/ui/shared/effect-frame.js +0 -146
- package/dist/ui/shared/effect-geometry.js +0 -137
- package/dist/ui/shared/effect-host-style.js +0 -10
- package/dist/ui/shared/effect-owner-regions.js +0 -74
- package/dist/ui/shared/effect-owner-source.js +0 -122
- package/dist/ui/shared/effect-policy.js +0 -151
- package/dist/ui/shared/effect-poster-reader.js +0 -98
- package/dist/ui/shared/effect-posters.js +0 -117
- package/dist/ui/shared/effect-profiler.js +0 -184
- package/dist/ui/shared/effect-profiles.js +0 -65
- package/dist/ui/shared/effect-registry.js +0 -545
- package/dist/ui/shared/effect-runtime-loader.js +0 -236
- package/dist/ui/shared/effect-safety.js +0 -153
- package/dist/ui/shared/effect-stock-snapshot.js +0 -43
- package/dist/ui/shared/effect-stock-trust.js +0 -217
- package/dist/ui/shared/effect-trial-shadow.js +0 -62
- package/dist/ui/shared/effect-trial-source.js +0 -103
- package/dist/ui/shared/effect-trial-stage.js +0 -170
- package/dist/ui/shared/effect-trial.css +0 -40
- package/dist/ui/shared/feature-flags.js +0 -127
- package/dist/ui/shared/owner-effect-prefs.js +0 -117
- package/dist/upgrade-notices.d.ts +0 -17
- package/dist/upgrade-notices.js +0 -93
- package/dist/upgrade-notices.js.map +0 -1
- package/dist/web-effect-bootstrap.d.ts +0 -12
- package/dist/web-effect-bootstrap.js +0 -49
- package/dist/web-effect-bootstrap.js.map +0 -1
- package/dist/web-effect-bundle.d.ts +0 -28
- package/dist/web-effect-bundle.js +0 -171
- package/dist/web-effect-bundle.js.map +0 -1
- package/dist/web-effect-canonical.d.ts +0 -8
- package/dist/web-effect-canonical.js +0 -27
- package/dist/web-effect-canonical.js.map +0 -1
- package/dist/web-effect-library.d.ts +0 -114
- package/dist/web-effect-library.js +0 -530
- package/dist/web-effect-library.js.map +0 -1
- package/dist/web-effect-playback.d.ts +0 -30
- package/dist/web-effect-playback.js +0 -71
- package/dist/web-effect-playback.js.map +0 -1
- package/dist/web-effect-poster.d.ts +0 -8
- package/dist/web-effect-poster.js +0 -52
- package/dist/web-effect-poster.js.map +0 -1
- package/dist/web-effect-profiles.d.ts +0 -118
- package/dist/web-effect-profiles.js +0 -181
- package/dist/web-effect-profiles.js.map +0 -1
- package/dist/web-effect-service.d.ts +0 -93
- package/dist/web-effect-service.js +0 -335
- package/dist/web-effect-service.js.map +0 -1
- package/dist/web-effect-tools.d.ts +0 -48
- package/dist/web-effect-tools.js +0 -129
- package/dist/web-effect-tools.js.map +0 -1
- package/dist/web-effect-trials.d.ts +0 -86
- package/dist/web-effect-trials.js +0 -204
- package/dist/web-effect-trials.js.map +0 -1
- package/dist/web-effect-upload.d.ts +0 -27
- package/dist/web-effect-upload.js +0 -185
- package/dist/web-effect-upload.js.map +0 -1
- package/dist/web-effect-worker.d.ts +0 -8
- package/dist/web-effect-worker.js +0 -170
- package/dist/web-effect-worker.js.map +0 -1
- package/dist/web-effect.d.ts +0 -103
- package/dist/web-effect.js +0 -153
- package/dist/web-effect.js.map +0 -1
- package/dist/web-experimental-http.d.ts +0 -2
- package/dist/web-experimental-http.js +0 -12
- package/dist/web-experimental-http.js.map +0 -1
- package/dist/web-mod-agent.d.ts +0 -50
- package/dist/web-mod-agent.js +0 -207
- package/dist/web-mod-agent.js.map +0 -1
package/dist/ui/fleet-office.js
CHANGED
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
// any part — an office names it as its style; one this page cannot load is drawn as Desks, and says so.
|
|
34
34
|
// - Crowds (#2513): an area with more people than seats packs the rest into a tighter grid, then counts who is left
|
|
35
35
|
// ("+N", in its corner, never over its name); there idle and asleep people say nothing, at most a few speak (calls
|
|
36
|
-
// first, then work), no bubble covers a name tag, and the tags
|
|
37
|
-
// (per browser) leaves the sleepers out.
|
|
36
|
+
// first, then work), no bubble covers a name tag, and the tags are compact (#2860; one that would touch another shows
|
|
37
|
+
// on hover or focus only). "Hide paused and stopped" (per browser) leaves the sleepers out.
|
|
38
38
|
// - Share… (#2636; an admin, on the dashboard reached directly or over the public link): a link anyone can open to watch an office live, read-only,
|
|
39
39
|
// until it ends (office-share-dialog.js; the page it opens is office-share-page.js).
|
|
40
40
|
// - Edit layout (an admin, a stored office on a zones skin): fleet-office-layout.js, saved as the office's overrides.
|
|
@@ -64,7 +64,7 @@ import { nodeText, stateWords } from "./fleet-org.js";
|
|
|
64
64
|
import { hits, needle } from "./fleet-canvas-layout.js";
|
|
65
65
|
import { AUTO_ID, LOBBY, autoLayout, backingScale, coatOf, createFlights, customLayout, deskAt, desksOf, filterOk, lampOf, layoutOk, lightOf, membersOf,
|
|
66
66
|
officePose, officeRooms, signOf, workOn } from "./fleet-office-model.js";
|
|
67
|
-
import { BUILT_IN_SKINS, CHAR_DEPTH, DEFAULT_PAL, bubbleBox, loadCozySkin, loadPackSkin, modeOf, skinFor, tagBox } from "./fleet-office-skin.js";
|
|
67
|
+
import { BUILT_IN_SKINS, CHAR_DEPTH, DEFAULT_PAL, bubbleBox, loadCozySkin, loadPackSkin, modeOf, skinFor, tagBox, tagFootprint } from "./fleet-office-skin.js";
|
|
68
68
|
import { CALLING, activeFurniture, bubbleAt, hiddenBy, bubbleRank, NIGHT, WANDER, createMail, createWalkers, isNight, linesFor, npcKey, personAt, placeBubbles, thinBubbles, walkTargets, zonesLayout } from "./fleet-office-zones.js";
|
|
69
69
|
import { actsOk, createOfficeStore } from "./fleet-office-store.js";
|
|
70
70
|
import { badgeOf, fixedOf } from "./fleet-office-levels.js";
|
|
@@ -131,6 +131,8 @@ const deviceRatio = () => Math.max(0.5, Math.min(4, Number(globalThis.devicePixe
|
|
|
131
131
|
const SIZE0 = { w: 960, h: 600 };
|
|
132
132
|
/** #2554: All offices' Fit keeps this many CSS px clear round the tiles (the zoom controls' column and a margin). */
|
|
133
133
|
const FIT_PAD = 60;
|
|
134
|
+
/** #2860: how far below its place a crowded area's compact tag drops when a neighbour's is in the way (a tag's height + 1). */
|
|
135
|
+
const COMPACT_ROW = 12;
|
|
134
136
|
/** #2554: in All offices a tile's name tags show (always) from this many CSS px a unit up; below it on hover only. */
|
|
135
137
|
export const TAG_MIN_K = 0.75;
|
|
136
138
|
const PAN_KEYS = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1] };
|
|
@@ -418,13 +420,19 @@ export function drawPart(ctx, p, a, ox, oy, ms, still, hot, env, tagsOff = false
|
|
|
418
420
|
if (sk.drawForeground) sk.drawForeground(ctx, p.L, env);
|
|
419
421
|
// Who found no spot in an area: a count in its corner (never over its name).
|
|
420
422
|
for (const [ar, n] of p.L.crowd || []) if (p.L.areas[ar] && sk.drawCrowd) sk.drawCrowd(ctx, p.L.areas[ar], n, env);
|
|
421
|
-
// In an area over capacity
|
|
423
|
+
// In an area over capacity (#2513: a crowd's full tags covered each other) everyone keeps a compact tag (#2860:
|
|
424
|
+
// in Cozy, the default, most of a fleet is in a crowd, and hover-only hid names and levels); one that would touch
|
|
425
|
+
// a tag already drawn, its badge included, is left to hover or focus. The full tags go first.
|
|
422
426
|
const over = p.L.over || new Set();
|
|
427
|
+
const crowded = (q) => over.has(q.d.area) && q.d.key !== hot && !q.look.match;
|
|
423
428
|
const tagged = (q) => !((tagsOff || over.has(q.d.area)) && q.d.key !== hot && !q.look.match);
|
|
429
|
+
const taken = [];
|
|
430
|
+
const touches = (b) => taken.some((o) => b.x < o.x + o.w && o.x < b.x + b.w && b.y < o.y + o.h && o.y < b.y + b.h);
|
|
424
431
|
for (const q of people) {
|
|
425
432
|
if (live.hidden.has(q.look.name) || !tagged(q)) continue;
|
|
426
433
|
sk.drawTag(ctx, q.at.x, q.at.y, q.look, env);
|
|
427
|
-
|
|
434
|
+
const box = tagBox(ctx, q.at.x, q.at.y, q.look.label);
|
|
435
|
+
tags.push(box); taken.push(tagFootprint(ctx, box, q.look.badge));
|
|
428
436
|
}
|
|
429
437
|
// A furniture item's own name (#2574: an NPC drawn as furniture), tagged like a person; none on a tile too small to read.
|
|
430
438
|
// #2698: an item hiding its people carries their tags too, in one row at its tag point (its own first; each keeps
|
|
@@ -434,7 +442,10 @@ export function drawPart(ctx, p, a, ox, oy, ms, still, hot, env, tagsOff = false
|
|
|
434
442
|
for (const f of furniture) {
|
|
435
443
|
const on = people.filter((q) => live.hidden.get(q.look.name) === f.id);
|
|
436
444
|
if (!on.length) {
|
|
437
|
-
if (!tagsOff && f.label) {
|
|
445
|
+
if (!tagsOff && f.label) {
|
|
446
|
+
const nl = npcLook(p, f), box = tagBox(ctx, f.x, f.y, nl.label);
|
|
447
|
+
sk.drawTag(ctx, f.x, f.y, nl, env); tags.push(box); taken.push(tagFootprint(ctx, box, nl.badge));
|
|
448
|
+
}
|
|
438
449
|
continue;
|
|
439
450
|
}
|
|
440
451
|
const order = new Map((f.activeFor || []).map((n, i) => [n, i]));
|
|
@@ -447,11 +458,24 @@ export function drawPart(ctx, p, a, ox, oy, ms, still, hot, env, tagsOff = false
|
|
|
447
458
|
if (q ? !tagged(q) : tagsOff) continue;
|
|
448
459
|
sk.drawTag(ctx, r.x, f.y, r.look, env);
|
|
449
460
|
const box = tagBox(ctx, r.x, f.y, r.look.label);
|
|
450
|
-
tags.push(box);
|
|
461
|
+
tags.push(box); taken.push(tagFootprint(ctx, box, r.look.badge));
|
|
451
462
|
if (q) held.push({ name: q.look.name, id: f.id, box });
|
|
452
463
|
}
|
|
453
464
|
}
|
|
454
465
|
p.heldTags = held;
|
|
466
|
+
// The crowd's compact tags go last, clear of every full tag above: people's, an item's own name and the row it
|
|
467
|
+
// carries (#2862 review). A crowd's grid is tighter than a compact tag is wide (OVERFLOW.STEPS down to 32): a
|
|
468
|
+
// neighbour's tag goes a row lower (COMPACT_ROW) rather than out, so a row of people reads as two staggered rows.
|
|
469
|
+
if (!tagsOff) for (const q of people) {
|
|
470
|
+
if (live.hidden.has(q.look.name) || !crowded(q)) continue;
|
|
471
|
+
for (const dy of [0, COMPACT_ROW]) {
|
|
472
|
+
const box = tagBox(ctx, q.at.x, q.at.y + dy, q.look.label, true), foot = tagFootprint(ctx, box, q.look.badge, true);
|
|
473
|
+
if (touches(foot)) continue;
|
|
474
|
+
sk.drawTag(ctx, q.at.x, q.at.y + dy, q.look, env, true);
|
|
475
|
+
tags.push(box); taken.push(foot);
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
455
479
|
} else {
|
|
456
480
|
for (const d of p.desks) { const look = p.looks.get(d.key); sk.drawCharacter(ctx, d, actPosed(p, look), ms, env); speakers.push({ look, x: d.x + 58, y: d.y + 12, area: p.roomOf.get(d.key) ?? null }); }
|
|
457
481
|
blit(p.layers.front);
|
|
@@ -3,24 +3,38 @@
|
|
|
3
3
|
// card. What runs is then handed to the preview frame exactly like a block's (AgendPreview.start): the file URL itself
|
|
4
4
|
// is never opened or framed on the dashboard's origin.
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* #2863: the sizes a preview takes (UTF-8 bytes of the HTML), the same as a block's (preview.js LIMITS): up to ASK it
|
|
8
|
+
* runs at once; above it the person is asked first ("Preview anyway?"); above MAX there is no preview, only the
|
|
9
|
+
* download. MAX is the frame's own ceiling (web-preview.ts PREVIEW_HTML_MAX_BYTES).
|
|
10
|
+
*/
|
|
11
|
+
export const HTML_PREVIEW_ASK = 8 * 1024 * 1024;
|
|
12
|
+
export const HTML_PREVIEW_MAX = 50 * 1024 * 1024;
|
|
8
13
|
|
|
9
14
|
/**
|
|
10
|
-
* Read an attachment's HTML: { ok: true, code } or { ok: false, reason } with reason "
|
|
11
|
-
*
|
|
12
|
-
*
|
|
15
|
+
* Read an attachment's HTML: { ok: true, code } or { ok: false, reason } with reason "large" (above `ask` and not
|
|
16
|
+
* `anyway`: nothing is fetched when its listed size says so; `size` is what it is), "over" (above `max`: its listed size
|
|
17
|
+
* is checked before anything is fetched, the bytes again after), "gone" (the fleet no longer serves that id — the file
|
|
18
|
+
* changed, or the fleet restarted), or "failed". The text card reads with its own, single cap (ask = max).
|
|
13
19
|
*/
|
|
14
|
-
export async function loadHtmlAttachment(att, fetchImpl = globalThis.fetch) {
|
|
20
|
+
export async function loadHtmlAttachment(att, fetchImpl = globalThis.fetch, { ask = HTML_PREVIEW_ASK, max = HTML_PREVIEW_MAX, anyway = false } = {}) {
|
|
15
21
|
if (!att || typeof att.id !== "string" || !/^[0-9a-f]{32}$/.test(att.id)) return { ok: false, reason: "failed" };
|
|
16
|
-
if (typeof att.size === "number" && att.size >
|
|
22
|
+
if (typeof att.size === "number" && att.size > max) return { ok: false, reason: "over" };
|
|
23
|
+
if (typeof att.size === "number" && att.size > ask && !anyway) return { ok: false, reason: "large", size: att.size };
|
|
17
24
|
let res;
|
|
18
25
|
try { res = await fetchImpl(`/ui/file/${att.id}`, { credentials: "same-origin", cache: "no-store" }); }
|
|
19
26
|
catch { return { ok: false, reason: "failed" }; }
|
|
20
27
|
if (!res || !res.ok) return { ok: false, reason: res && res.status === 404 ? "gone" : "failed" };
|
|
21
28
|
let bytes;
|
|
22
29
|
try { bytes = await res.arrayBuffer(); } catch { return { ok: false, reason: "failed" }; }
|
|
23
|
-
if (bytes.byteLength >
|
|
30
|
+
if (bytes.byteLength > max) return { ok: false, reason: "over" };
|
|
31
|
+
if (bytes.byteLength > ask && !anyway) return { ok: false, reason: "large", size: bytes.byteLength };
|
|
24
32
|
// UTF-8, as a block's text is; a BOM is dropped.
|
|
25
33
|
return { ok: true, code: new TextDecoder("utf-8").decode(bytes) };
|
|
26
34
|
}
|
|
35
|
+
|
|
36
|
+
/** #2863: a size as the person reads it: "12.3 MB" (1 MB = 1024 × 1024 bytes, as the caps are). */
|
|
37
|
+
export function sizeLabel(bytes) {
|
|
38
|
+
const mb = bytes / (1024 * 1024);
|
|
39
|
+
return `${mb >= 10 ? Math.round(mb) : Math.round(mb * 10) / 10} MB`;
|
|
40
|
+
}
|
package/dist/ui/panel-chat.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
// right, the agent's at full width, and one rounded composer pinned below with attach, Stop reply and Send inside it.
|
|
7
7
|
// The instance's own actions (details, start, stop, restart, delete) are in the header's ⋯ menu, and only the ones
|
|
8
8
|
// that apply to its state are offered (#1408 rough edges 4 and 7).
|
|
9
|
-
import { html,
|
|
9
|
+
import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
|
|
10
10
|
import { lang, t } from "/assets/app-i18n.js";
|
|
11
|
-
import { appStore, createStore, guestPage, useStore } from "/assets/app-store.js";
|
|
11
|
+
import { appStore, createStore, guestPage, guestTrusted, useStore } from "/assets/app-store.js";
|
|
12
12
|
import { ComposerAuth } from "/assets/web-authorize.js";
|
|
13
13
|
import { useLease } from "/assets/app-ctx.js";
|
|
14
14
|
import { modelText, namesOf, namesStore, navStore as instanceNavStore } from "/assets/instance-nav.js";
|
|
@@ -80,12 +80,7 @@ import { PreviewPanel, claimPanel, openPanel, panelClaims, panelStore, panelVers
|
|
|
80
80
|
import { needsArgument, paletteFor, parseCommandLine } from "./chat-commands.js";
|
|
81
81
|
import { installTour, refreshTourSpot } from "./chat-tour.js";
|
|
82
82
|
import { LightboxHost, openLightbox } from "./image-lightbox.js";
|
|
83
|
-
import { canvasWorkerMenuAvailable, experimentalComponent, useCanvasWorker } from "/assets/feature-flags.js";
|
|
84
|
-
const ChatEffectPosters = experimentalComponent(() => import("./chat-effect-posters.js"), m => m.ChatEffectPosters);
|
|
85
|
-
const ChatEffects = experimentalComponent(() => import("./chat-effects.js"), m => m.ChatEffects);
|
|
86
|
-
import { setFollowsReducedMotion } from "/assets/fx-motion.js";
|
|
87
83
|
import { AmbientMotionNote, ChatAmbient, ambientMenuItems } from "./chat-ambient.js";
|
|
88
|
-
import { startEffectsWaker, getAppEffects, wakeEffects, effectsLoaded, onEffectsAwake, chatHasEffects } from "/assets/feature-flags.js";
|
|
89
84
|
/** The composer's textarea (#2342: text put back into it is a step Ctrl+Z goes back from). */
|
|
90
85
|
const composerField = () => (typeof document !== "undefined" ? document.getElementById("msgIn") : null);
|
|
91
86
|
|
|
@@ -149,9 +144,6 @@ export function boot({ stream, boot: bootData, deps = {} }) {
|
|
|
149
144
|
}
|
|
150
145
|
// #1604: the preview opt-in and Tour live in Settings → This device now (and on each preview card's menu).
|
|
151
146
|
installTour();
|
|
152
|
-
// #2118: viewing current in-scope Chat effects needs no management or personal-slot authority. #2193: the runtime
|
|
153
|
-
// itself loads only when there is something to play (effects-waker.js).
|
|
154
|
-
startEffectsWaker({ store });
|
|
155
147
|
return store;
|
|
156
148
|
}
|
|
157
149
|
|
|
@@ -261,9 +253,6 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
261
253
|
const themeKey = JSON.stringify(theme);
|
|
262
254
|
const card = useCard(name, inst.card_v);
|
|
263
255
|
const [dialog, setDialog] = useState(null); // "delete" | null
|
|
264
|
-
const [canvasPlayback, setCanvasPlayback] = useState(null);
|
|
265
|
-
const publishCanvasPlayback = useCallback(value => setCanvasPlayback(previous =>
|
|
266
|
-
!previous && !value.playing || previous?.name === value.name && previous.playing === value.playing ? previous : value), []);
|
|
267
256
|
const [wrap, setWrap] = useState(codeWrap);
|
|
268
257
|
const wide = useStore(chatWide).on;
|
|
269
258
|
const view = useRef(null), split = useRef(null), thread = useRef(null), bg = useRef(null), effectSurface = useRef(null);
|
|
@@ -312,9 +301,6 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
312
301
|
else if (r && r.held) toast(r.message);
|
|
313
302
|
else toast(t(verb === "start" ? "chat.started" : verb === "stop" ? "chat.stoppedInst" : "chat.restarted", name));
|
|
314
303
|
};
|
|
315
|
-
// #2193: this chat's Canvas Worker placements (its status row's effect_chat_v): none, no effects code runs for it.
|
|
316
|
-
const cwEnabled = useCanvasWorker();
|
|
317
|
-
const fxHere = cwEnabled && chatHasEffects(name);
|
|
318
304
|
const items = [
|
|
319
305
|
// #1523 N2: Details is a page now (the Fleet side of this instance), not a dialog.
|
|
320
306
|
{ key: "details", label: t("chat.details"), icon: "info", onSelect: () => navigate(detailsPath(name)) },
|
|
@@ -328,7 +314,6 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
328
314
|
// #2117: its ambient — why it is not showing here (with the way to change that), and a person's Clear.
|
|
329
315
|
...ambientMenuItems(name, { publicLink: document.body.dataset.publicLink === "1", clear: clearAmbient }),
|
|
330
316
|
narrowScreen() ? null : { key: "fullWidth", checkbox: true, checked: wide, label: t("chat.fullWidth"), onSelect: () => setChatWide(!wide) },
|
|
331
|
-
cwEnabled && canvasWorkerMenuAvailable(window) ? { key: "effects", label: t("cwControls.title"), onSelect: () => setDialog("effects") } : null,
|
|
332
317
|
{ key: "delete", label: t("chat.delete"), icon: "trash", danger: true, onSelect: () => setDialog("delete") },
|
|
333
318
|
];
|
|
334
319
|
const cls = statusClass(inst, exec, awaiting);
|
|
@@ -341,14 +326,14 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
341
326
|
${inst.model ? html`<button type="button" class="hd-chip" title=${t("chat.chipModel", modelText(inst))} aria-label=${t("chat.chipModel", modelText(inst))} onClick=${() => pick("model")}>${modelText(inst)}</button>` : null}
|
|
342
327
|
${inst.effort ? html`<button type="button" class="hd-chip" title=${t("chat.chipEffort", inst.effort)} aria-label=${t("chat.chipEffort", inst.effort)} onClick=${() => pick("effort")}>${inst.effort}</button>` : null}`;
|
|
343
328
|
return html`<div class=${`panel p-chat${wrap ? " wrap-code" : ""}`} data-width=${wide ? "full" : undefined} ref=${view}>
|
|
344
|
-
<${PanelHeader} title=${headerName(namesOf(inst || { name }, names.primary))} sub=${sub} nav=${guest ?
|
|
329
|
+
<${PanelHeader} title=${headerName(namesOf(inst || { name }, names.primary))} sub=${sub} nav=${guest ? (guestTrusted() ? html`<${InstanceSwitch} name=${name} current="chat" guest=${true} />` : null) : html`<${InstanceSwitch} name=${name} current="chat" />`}><${CardMenu} card=${card} /><${HeaderTools} navKey=${navKey} />${guest ? null : html`<${Menu} items=${items} label=${t("app.more")} />`}</${PanelHeader}>
|
|
345
330
|
<${ChatCard} key=${name} name=${name} card=${card} />
|
|
346
331
|
<${AgentWidget} slot="chat_header" widget=${widgetFor(name, "chat_header")} binds=${instanceBinds(inst, exec, awaiting)} />
|
|
347
332
|
<div class="chat-split" ref=${split}>
|
|
348
333
|
<div ref=${effectSurface} class=${`chat-main${theme && theme.background ? " has-th-bg" : ""}`}>
|
|
349
334
|
${theme && theme.background ? html`<img ref=${bg} class=${`th-bg th-bg-${theme.background.fit}`} src=${themeImageUrl(name, theme.background.image)} alt="" aria-hidden="true" decoding="async" />` : null}
|
|
350
335
|
<${ChatAmbient} name=${name} surface=${effectSurface} part="layer" />
|
|
351
|
-
<${AmbientMotionNote} name=${name}
|
|
336
|
+
<${AmbientMotionNote} name=${name} />
|
|
352
337
|
<${Thread} name=${name} th=${thread} />
|
|
353
338
|
<${Dock} name=${name} inst=${inst} lease=${lease} exec=${exec} awaiting=${awaiting} workingLabel=${theme ? theme.workingLabel : undefined} />
|
|
354
339
|
<${ChatAmbient} name=${name} surface=${effectSurface} part="front" />
|
|
@@ -423,20 +408,11 @@ function threadEntry(name, doc) {
|
|
|
423
408
|
list.setAttribute("aria-label", t("chat.conversationWith", name));
|
|
424
409
|
const e = { list, sink: null, lang: lang(), thread: null };
|
|
425
410
|
const thread = createThread(list, doc.createElement("div"), {
|
|
426
|
-
t, tf: t, isUser: (x) => store.isUser(x, name), onJump: (j) => e.sink?.setJump(j),
|
|
411
|
+
t, tf: t, isUser: (x) => store.isUser(x, name), onJump: (j) => e.sink?.setJump(j),
|
|
412
|
+
// #2848: which empty — "hidden" (a guest's chat whose every message is hidden from it) differs from a plain empty
|
|
413
|
+
// chat, so the explanation shows when the history answer comes after the first (empty) draw.
|
|
414
|
+
onEmpty: (v) => e.sink?.setEmpty(v && store.allHidden(name) ? "hidden" : v),
|
|
427
415
|
setPreviewOptIn, copyText, download: downloadHtml, toggleWrap,
|
|
428
|
-
// #2193: an offer shown while the effects runtime sleeps wakes it; until it answers, the offer is being checked.
|
|
429
|
-
effectTrialStatus: x => { const fx = getAppEffects(); if (fx) return fx.trialStatus(x); if (!effectsLoaded()) { void wakeEffects(); return "checking"; } return "localAdmin"; },
|
|
430
|
-
effectTrialFailure: x => getAppEffects()?.trialFailure(x),
|
|
431
|
-
fixEffectTrial: (x, reason) => {
|
|
432
|
-
if (reason === "reducedMotion") setFollowsReducedMotion(false);
|
|
433
|
-
else if (reason === "busy") getAppEffects()?.stopTrial();
|
|
434
|
-
else if (reason === "transport") location.reload();
|
|
435
|
-
else if (reason === "localAdmin") location.href = "/signin?next=" + encodeURIComponent(location.pathname + location.search);
|
|
436
|
-
else navigate("/settings/look");
|
|
437
|
-
},
|
|
438
|
-
canTryEffect: x => getAppEffects()?.canTry(x) === true,
|
|
439
|
-
startEffectTrial: x => { const seq = navStore.get().seq; void getAppEffects()?.startTrial(x).then(ok => { if (!ok && e.sink && navStore.get().seq === seq && document.hasFocus?.() === true && document.visibilityState === "visible") toast(t("chat.effectTrialUnavailable")); }); },
|
|
440
416
|
clickReplyButton: (id, index) => store.clickReplyButton(name, id, index), // #1266
|
|
441
417
|
retryOutgoing: (clientId) => store.retryOutgoing(name, clientId), // a failed send's Retry and Delete
|
|
442
418
|
deleteOutgoing: (clientId) => store.deleteOutgoing(name, clientId),
|
|
@@ -472,17 +448,6 @@ function follow(name, thread) {
|
|
|
472
448
|
else if (kind === "sent") thread.jumpLatest();
|
|
473
449
|
}));
|
|
474
450
|
offs.push(P() ? P().onChange(() => thread.refreshCards()) : () => {});
|
|
475
|
-
// #2193: the runtime may start later (an offer shown wakes it): its trial updates are followed from then.
|
|
476
|
-
let trialOff = null, trialOwner = null;
|
|
477
|
-
const watchTrial = () => {
|
|
478
|
-
const next = getAppEffects();
|
|
479
|
-
if (next === trialOwner) return;
|
|
480
|
-
trialOff?.(); trialOff = null; trialOwner = next;
|
|
481
|
-
trialOff = next?.subscribeTrial(() => thread.render(store.threadRows(name))) ?? null;
|
|
482
|
-
};
|
|
483
|
-
watchTrial();
|
|
484
|
-
offs.push(onEffectsAwake(() => { watchTrial(); thread.render(store.threadRows(name)); }));
|
|
485
|
-
offs.push(() => { trialOff?.(); trialOff = null; trialOwner = null; });
|
|
486
451
|
offs.push(panelStore.subscribe(() => thread.refreshCards())); // a card shows whether the panel has its block
|
|
487
452
|
return () => { for (const off of offs) off(); };
|
|
488
453
|
}
|
|
@@ -548,7 +513,8 @@ function Thread({ name, th }) {
|
|
|
548
513
|
}, [name]);
|
|
549
514
|
return html`<div class="scroller" ref=${scroller}>
|
|
550
515
|
<div class="thread-host" ref=${host}></div>
|
|
551
|
-
${empty ? html`<${Empty} icon="chat" title=${t("chat.
|
|
516
|
+
${empty === "hidden" ? html`<${Empty} icon="chat" title=${t("chat.guestAllHidden")} hint=${t("chat.guestAllHiddenHint")} />`
|
|
517
|
+
: empty ? html`<${Empty} icon="chat" title=${t("chat.noMessages")} />` : null}
|
|
552
518
|
<div class="jump-dock">${jump.show ? html`<button data-trust="thread-control" type="button" class="jump-latest" onClick=${() => th.current && th.current.jumpLatest()}>
|
|
553
519
|
<${Icon} name="down" size=${16} />${jump.unseen > 0 ? t("chat.newBelow", jump.unseen) : t("chat.jumpLatest")}</button>` : null}</div>
|
|
554
520
|
</div>`;
|
|
@@ -677,10 +643,12 @@ function Composer({ name, label = name, lease, busy }) {
|
|
|
677
643
|
const files = s.pendingFiles[name] || [];
|
|
678
644
|
const reply = s.replies[name]; // #2272: the message this one will reply to
|
|
679
645
|
const content = !!value.trim() || files.length > 0;
|
|
680
|
-
// A file waiting: what goes is a message with its file, never a command (no palette, no command line).
|
|
681
|
-
//
|
|
646
|
+
// A file waiting: what goes is a message with its file, never a command (no palette, no command line). #2855: a guest's
|
|
647
|
+
// palette offers only what it may run on this agent — the table's "anyone" commands (its status frame's `commands`).
|
|
682
648
|
const guest = guestPage();
|
|
683
|
-
const
|
|
649
|
+
const app = useStore(appStore);
|
|
650
|
+
const guestAllowed = guest ? (app.instances.find((i) => i.name === name)?.commands ?? []) : null;
|
|
651
|
+
const palette = files.length || closedFor === value || (guest && !guestAllowed.length) ? null : paletteFor(value, guestAllowed || undefined);
|
|
684
652
|
const pick = palette && palette.length ? palette[Math.min(sel, palette.length - 1)] : null;
|
|
685
653
|
// Grow with the text, up to a limit: set through the CSSOM, never a style attribute (#1300). Empty, it is one line
|
|
686
654
|
// (#2478 — a long name's placeholder made the box two lines tall).
|
|
@@ -707,9 +675,12 @@ function Composer({ name, label = name, lease, busy }) {
|
|
|
707
675
|
else complete(pick);
|
|
708
676
|
return;
|
|
709
677
|
}
|
|
710
|
-
// #1920: a guest's "/…" would be refused as a command — said here, the draft kept to edit.
|
|
711
|
-
if (guest && /^\s*\//.test(value)) { toast(t("chat.guestNoCommands"), false); return; }
|
|
712
678
|
const cmd = files.length ? null : parseCommandLine(value);
|
|
679
|
+
// #1920/#2855: a guest's "/…" that is not one it may run here is refused here, why said, the draft kept to edit — a
|
|
680
|
+
// known command: only the channel's admins run it; anything else: it would not go as a message either.
|
|
681
|
+
if (guest && /^\s*\//.test(value) && !(cmd && guestAllowed.includes(cmd.command))) {
|
|
682
|
+
toast(cmd ? t("chat.guestCommandNotAllowed", `/${cmd.command}`) : t("chat.guestNoCommands"), false); return;
|
|
683
|
+
}
|
|
713
684
|
if (cmd && needsArgument(cmd.command) && !cmd.args) { recordEdit(input.current); setValue(`/${cmd.command} `); return; }
|
|
714
685
|
if (cmd) { runLine(cmd); return; }
|
|
715
686
|
store.send(name);
|
|
@@ -743,7 +714,7 @@ function Composer({ name, label = name, lease, busy }) {
|
|
|
743
714
|
${palette.length ? palette.map((c) => html`<li key=${c.name} id=${optId(c)} role="option" aria-selected=${c === pick ? "true" : "false"}
|
|
744
715
|
class=${c === pick ? "on" : ""} onMouseDown=${(e) => { e.preventDefault(); complete(c); }}>
|
|
745
716
|
<span class="cmd-name mono">/${c.name}${c.arg ? html` <span class="cmd-arg">${c.arg}</span>` : null}</span><span class="cmd-desc">${t(`chat.cmd_${c.name}`)}</span></li>`)
|
|
746
|
-
: html`<li class="cmd-none" role="option" aria-selected="false">${t("chat.cmdNotCommand")}</li>`}
|
|
717
|
+
: html`<li class="cmd-none" role="option" aria-selected="false">${t(guest ? "chat.cmdNotGuests" : "chat.cmdNotCommand")}</li>`}
|
|
747
718
|
</ul>` : null}
|
|
748
719
|
${reply ? html`<div class="reply-bar" data-trust="composer" role="status">
|
|
749
720
|
<span class="reply-mark" aria-hidden="true"><svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/></svg></span><span class="reply-label">${t("chat.replyingTo", reply.sender)}</span>
|
package/dist/ui/panel-log.js
CHANGED
|
@@ -58,7 +58,7 @@ register("log", {
|
|
|
58
58
|
ev_instance_paused: "Paused", ev_instance_resumed: "Resumed", ev_interactive_prompt: "Waiting at a prompt", ev_interactive_prompt_assist: "Prompt: asked for help", ev_remote_card: "Asked a question (posted to chat)", ev_remote_card_answer: "Question answered from chat",
|
|
59
59
|
ev_dialog_parked: "A dialog waits", ev_model_failover: "Switched to another model", ev_model_recovered: "Model back",
|
|
60
60
|
ev_mcp_died: "Tool server stopped", ev_mcp_recovered: "Tool server back", ev_mcp_auto_restart: "Tool server restarted",
|
|
61
|
-
ev_message_failed: "A message was not delivered", ev_pty_error: "Terminal error", ev_pty_recovered: "Terminal recovered",
|
|
61
|
+
ev_message_failed: "A message was not delivered", ev_messages_cancelled: "Messages dropped by a cancel", ev_pty_error: "Terminal error", ev_pty_recovered: "Terminal recovered",
|
|
62
62
|
ev_backend_unreachable: "CLI unreachable", ev_startup_backend_unreachable: "CLI unreachable at start", ev_backend_launch_warning: "Launch warning",
|
|
63
63
|
ev_clear_action: "Conversation cleared", ev_normal_exit_action: "Exited", ev_reply_drop_detected: "A reply went missing",
|
|
64
64
|
ev_reply_drop_recovered: "Missing reply recovered", ev_reply_drop_unrecovered: "Missing reply not recovered",
|
|
@@ -96,7 +96,7 @@ register("log", {
|
|
|
96
96
|
ev_instance_paused: "已暫停", ev_instance_resumed: "已繼續", ev_interactive_prompt: "停在提示上等待", ev_interactive_prompt_assist: "提示:已請求協助", ev_remote_card: "提出問題(已貼到聊天)", ev_remote_card_answer: "問題已從聊天回答",
|
|
97
97
|
ev_dialog_parked: "有對話框在等待", ev_model_failover: "改用另一個模型", ev_model_recovered: "模型恢復",
|
|
98
98
|
ev_mcp_died: "工具伺服器停止", ev_mcp_recovered: "工具伺服器恢復", ev_mcp_auto_restart: "工具伺服器已重新啟動",
|
|
99
|
-
ev_message_failed: "有訊息沒有送達", ev_pty_error: "終端機錯誤", ev_pty_recovered: "終端機恢復",
|
|
99
|
+
ev_message_failed: "有訊息沒有送達", ev_messages_cancelled: "取消時丟掉的訊息", ev_pty_error: "終端機錯誤", ev_pty_recovered: "終端機恢復",
|
|
100
100
|
ev_backend_unreachable: "連不到 CLI", ev_startup_backend_unreachable: "啟動時連不到 CLI", ev_backend_launch_warning: "啟動警告",
|
|
101
101
|
ev_clear_action: "對話已清除", ev_normal_exit_action: "已結束", ev_reply_drop_detected: "有回覆遺失",
|
|
102
102
|
ev_reply_drop_recovered: "遺失的回覆已補回", ev_reply_drop_unrecovered: "遺失的回覆沒有補回",
|
package/dist/ui/panel-needs.js
CHANGED
|
@@ -19,7 +19,7 @@ import { namesOf, namesStore } from "/assets/instance-nav.js";
|
|
|
19
19
|
import { Empty } from "/assets/ui-states.js";
|
|
20
20
|
import { Icon } from "/assets/ui-icons.js";
|
|
21
21
|
import { toast } from "/assets/ui-toast.js";
|
|
22
|
-
import { notifyStore, notifySupport, reasonText, refreshNotify, setNotify } from "/assets/app-needs.js";
|
|
22
|
+
import { notifyStore, notifySupport, configDetail, reasonText, refreshNotify, setNotify } from "/assets/app-needs.js";
|
|
23
23
|
import { openSignIn, signInOffered } from "./backend-signin.js";
|
|
24
24
|
|
|
25
25
|
register("needs", {
|
|
@@ -95,7 +95,7 @@ const BAD = new Set(["crashed", "delivery_failed", "hang", "exited"]);
|
|
|
95
95
|
*/
|
|
96
96
|
function detailOf(item) {
|
|
97
97
|
const c = item.type === "task" ? item.card : null;
|
|
98
|
-
if (!c || !c.title) return item
|
|
98
|
+
if (!c || !c.title) return configDetail(item);
|
|
99
99
|
const lacks = c.gap ? [c.gap.unchecked ? tn("gapUnchecked", c.gap.unchecked) : "", c.gap.noResult ? tn("gapNoResult") : ""].filter(Boolean).join(tn("gapJoin")) : "";
|
|
100
100
|
const tail = c.question || (lacks ? tn("gap", lacks) : "");
|
|
101
101
|
return `${c.code} ${c.title}${tail ? ` — ${tail}` : ""}`;
|
|
@@ -112,13 +112,15 @@ export function age(since, nowMs) {
|
|
|
112
112
|
|
|
113
113
|
/** The store's list, grouped by instance, in its order (instances by their oldest item; items oldest first). */
|
|
114
114
|
export function groupNeeds(items) {
|
|
115
|
-
const groups =
|
|
115
|
+
const groups = new Map();
|
|
116
116
|
for (const item of items) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
// Configuration issues belong to Settings, not the unassigned task/sign-in group.
|
|
118
|
+
const key = JSON.stringify([item.instance, !item.instance && item.type === "config"]);
|
|
119
|
+
const group = groups.get(key);
|
|
120
|
+
if (group) group.items.push(item);
|
|
121
|
+
else groups.set(key, { instance: item.instance, items: [item] });
|
|
120
122
|
}
|
|
121
|
-
return groups;
|
|
123
|
+
return [...groups.values()];
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
export function NeedsPanel({ navKey }) {
|
|
@@ -142,8 +144,8 @@ export function NeedsPanel({ navKey }) {
|
|
|
142
144
|
<${PanelHeader} title=${tn("title")} sub=${items.length ? t("app.needsCount", items.length) : null} />
|
|
143
145
|
<div data-trust="panel-content" class="panel-body"><div class="col">
|
|
144
146
|
${!items.length ? html`<${Empty} icon="check" title=${tn("empty")} hint=${tn(chatApp ? "emptyHint" : "emptyHintWeb")} />`
|
|
145
|
-
: groups.map((g) => html`<section key=${g.instance} class="n-group" aria-label=${g.instance}>
|
|
146
|
-
<h2 class="n-group-head">${g.instance ? html`<a href=${chatPath(g.instance)} title=${g.instance}>${nameFor(g.instance)}</a>` : tn(g.items.some(i => i.type === "backend") ? "fleetSignIns" : "noAssignee")}</h2>
|
|
147
|
+
: groups.map((g) => html`<section key=${JSON.stringify([g.instance, g.items[0]?.type === "config"])} class="n-group" aria-label=${g.instance}>
|
|
148
|
+
<h2 class="n-group-head">${g.instance ? html`<a href=${chatPath(g.instance)} title=${g.instance}>${nameFor(g.instance)}</a>` : g.items[0]?.type === "config" ? t("app.settings") : tn(g.items.some(i => i.type === "backend") ? "fleetSignIns" : "noAssignee")}</h2>
|
|
147
149
|
${g.items.map((item) => html`<${NeedsItem} key=${item.id} item=${item} nowMs=${nowMs} owner=${owner} />`)}
|
|
148
150
|
</section>`)}
|
|
149
151
|
<${NotifyToggle} />
|
|
@@ -50,6 +50,7 @@ import { GuestsCard } from "./settings-guests.js";
|
|
|
50
50
|
import { OfficeSharesCard } from "./office-share-dialog.js";
|
|
51
51
|
import { syncStage, useDraft } from "./settings-draft.js";
|
|
52
52
|
import { WebAuthorityCard } from "./settings-web-authority.js";
|
|
53
|
+
import { NamedLinkTest, setTypedTunnelToken } from "./settings-named-test.js";
|
|
53
54
|
import { SignInList } from "./backend-signin.js";
|
|
54
55
|
|
|
55
56
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
@@ -1353,6 +1354,7 @@ function WebSection({ ctx, staged, part = "all" }) {
|
|
|
1353
1354
|
<div class=${fc("field", "originPort")}><label for="publicWebOriginPort">${tn("namedOriginPort")}</label><input id="publicWebOriginPort" type="number" min="1024" max="65535" value=${f.originPort} class=${errors.originPort ? "invalid" : ""} onInput=${(e) => set("originPort")(e.target.value)} /><${Feedback} error=${errors.originPort} /></div>
|
|
1354
1355
|
</div>
|
|
1355
1356
|
<p class="note">${tn("namedTunnelHint")} <a href="https://github.com/songsid/AgEnD/blob/main/docs/named-tunnel.md" target="_blank" rel="noopener noreferrer">${tn("namedTunnelSetup")}</a></p>
|
|
1357
|
+
${String(f.hostname || "").trim() ? html`<${NamedLinkTest} hostname=${f.hostname} port=${f.originPort} protocol=${f.protocol} />` : null}
|
|
1356
1358
|
<div class="grid2">
|
|
1357
1359
|
<div class=${fc("field", "ttl")}><label for="publicWebTtl">${tn(named ? "publicTtlNamed" : "publicTtl")}</label><input id="publicWebTtl" type="number" min=${named ? "0" : "1"} max=${named ? "10080" : "480"} step="1" class=${errors.ttl ? "invalid" : ""} value=${f.ttl} onInput=${(e) => set("ttl")(e.target.value)} /><${Feedback} error=${errors.ttl} /></div>
|
|
1358
1360
|
<div class=${fc("field", "protocol")}><label for="publicWebProtocol">${tn("publicProtocol")}</label><${Select} id="publicWebProtocol" value=${f.protocol} onChange=${set("protocol")} options=${["http2", "quic", "auto"]} /></div>
|
|
@@ -1548,11 +1550,11 @@ function PublicTunnelToken() {
|
|
|
1548
1550
|
const result = await api(base);
|
|
1549
1551
|
if (lease.current() && result.ok) setPresent(result.body?.[0]?.token_present === true);
|
|
1550
1552
|
};
|
|
1551
|
-
useEffect(() => { refresh().catch(() => {}); }, []);
|
|
1553
|
+
useEffect(() => { refresh().catch(() => {}); return () => setTypedTunnelToken(""); }, []);
|
|
1552
1554
|
const go = async (clear) => {
|
|
1553
1555
|
if (inFlight.current || !clear && !secret) return;
|
|
1554
1556
|
const value = secret;
|
|
1555
|
-
setSecret(""); inFlight.current = true; setBusy(true); setMessage("");
|
|
1557
|
+
setSecret(""); setTypedTunnelToken(""); inFlight.current = true; setBusy(true); setMessage("");
|
|
1556
1558
|
const key = newKey("tunnel-token");
|
|
1557
1559
|
try {
|
|
1558
1560
|
const prepared = await api(`${base}/verify`, { method: "POST", headers: { "Idempotency-Key": key }, body: JSON.stringify(clear ? { clear: true } : { secret: value }) });
|
|
@@ -1576,7 +1578,7 @@ function PublicTunnelToken() {
|
|
|
1576
1578
|
return html`<section class="card"><h3>${tn("namedToken")}</h3>
|
|
1577
1579
|
<p class="note">${tn("namedTokenHint")}</p>
|
|
1578
1580
|
<span class="tag">${present ? tn("keyConfigured") : tn("keyNotConfigured")}</span>
|
|
1579
|
-
<input id="publicTunnelToken" type="password" autocomplete="new-password" aria-label=${tn("namedToken")} value=${secret} onInput=${e => setSecret(e.target.value)} />
|
|
1581
|
+
<input id="publicTunnelToken" type="password" autocomplete="new-password" aria-label=${tn("namedToken")} value=${secret} onInput=${e => { setSecret(e.target.value); setTypedTunnelToken(e.target.value); }} />
|
|
1580
1582
|
<button type="button" class="btn btn-sm" disabled=${busy || !secret} onClick=${() => go(false)}>${tn("namedTokenSet")}</button>
|
|
1581
1583
|
<button type="button" class="btn btn-sm" disabled=${busy || !present} onClick=${() => go(true)}>${tn("namedTokenClear")}</button>
|
|
1582
1584
|
${message ? html`<p class="note" role="status">${message}</p>` : null}
|
package/dist/ui/preview-panel.js
CHANGED
|
@@ -16,8 +16,6 @@ import { Icon } from "/assets/ui-icons.js";
|
|
|
16
16
|
import "./chat-render.js";
|
|
17
17
|
import "./preview.js";
|
|
18
18
|
import { loadHtmlAttachment } from "./html-attachment.js";
|
|
19
|
-
import { experimentalComponent, useCanvasWorker } from "/assets/feature-flags.js";
|
|
20
|
-
const EffectTrialPanel = experimentalComponent(() => import("./effect-trial-panel.js"), m => m.EffectTrialPanel);
|
|
21
19
|
import { chatTime, onDayChange } from "./chat-time.js";
|
|
22
20
|
|
|
23
21
|
const R = () => globalThis.AgendChatRender;
|
|
@@ -47,19 +45,13 @@ export const frameKey = (key) => `panel:${key}`;
|
|
|
47
45
|
/** Show `spec` in the panel; `run`: start it there (the card's "Open in panel" click). The block shown before stops. */
|
|
48
46
|
function retire(open) {
|
|
49
47
|
if (!open) return;
|
|
50
|
-
if (open.
|
|
51
|
-
else if (P()) P().stop(frameKey(open.key), "stopped", "");
|
|
48
|
+
if (P()) P().stop(frameKey(open.key), "stopped", "");
|
|
52
49
|
}
|
|
53
50
|
export function openPanel(spec, { run = false } = {}) {
|
|
54
51
|
const old = state.open;
|
|
55
52
|
set({ open: { ...spec, kind: "html" }, run: run ? ++runs : 0 });
|
|
56
53
|
retire(old); // State changes first: cleanup reentry cannot close a newer panel.
|
|
57
54
|
}
|
|
58
|
-
/** A checked controller handle enters the existing component slot; no HTML run token is issued. */
|
|
59
|
-
export function openTrialPanel(spec) {
|
|
60
|
-
const old = state.open;
|
|
61
|
-
set({ open: { ...spec, kind: "trial" }, run: 0 }); retire(old);
|
|
62
|
-
}
|
|
63
55
|
/** Close publishes the empty slot before old-owner callbacks can reenter. */
|
|
64
56
|
export function closePanel() {
|
|
65
57
|
const old = state.open; if (!old) return;
|
|
@@ -80,7 +72,7 @@ export function htmlTitle(code) {
|
|
|
80
72
|
* without one, the newest block does. `msgs` in order; `keyOf` makes a message's key.
|
|
81
73
|
*/
|
|
82
74
|
export function newerVersion(open, msgs, keyOf) {
|
|
83
|
-
if (!open ||
|
|
75
|
+
if (!open || !Array.isArray(msgs)) return null;
|
|
84
76
|
const at = msgs.findIndex(x => keyOf(x) === open.msgKey);
|
|
85
77
|
if (at < 0) return null;
|
|
86
78
|
// An attached file: a later agent reply attaching a file of the same name is its newer version. Its HTML is read
|
|
@@ -145,8 +137,7 @@ export function PreviewPanel({ name, split, msgs, subscribe, keyOf, reveal, down
|
|
|
145
137
|
const pending = useRef(0); // #1704: the card's click, waiting for this device's Allow (the st.run it came with)
|
|
146
138
|
const ownAllow = useRef(0); // while this panel's own Allow commits its choice: the click it answers (not cancelled by it)
|
|
147
139
|
const holder = useRef(null), divider = useRef(null), panel = useRef(null);
|
|
148
|
-
const
|
|
149
|
-
const open = st.open?.kind !== "trial" && st.open?.instance === name ? st.open : null;
|
|
140
|
+
const open = st.open?.instance === name ? st.open : null;
|
|
150
141
|
|
|
151
142
|
useEffect(() => panelStore.subscribe(setSt), []);
|
|
152
143
|
// This device's choice (here or in another tab) decides whether Preview is offered: re-read it when it changes.
|
|
@@ -161,11 +152,12 @@ export function PreviewPanel({ name, split, msgs, subscribe, keyOf, reveal, down
|
|
|
161
152
|
const [, dayTick] = useState(0);
|
|
162
153
|
useEffect(() => (open ? onDayChange(() => dayTick(n => n + 1)) : undefined), [!!open]);
|
|
163
154
|
|
|
164
|
-
|
|
165
|
-
const
|
|
155
|
+
// #2863: "large" — over 8 MB, nothing runs until "Preview anyway" (or the card's, which opened it with `anyway`).
|
|
156
|
+
const ui = { state: (s, reason) => setRun({ state: s === "starting" || s === "running" || s === "large" ? s : "idle", reason: reason || "" }) };
|
|
157
|
+
const start = (anyway) => {
|
|
166
158
|
if (!open || !holder.current) return;
|
|
167
159
|
setView("preview");
|
|
168
|
-
P().start(frameKey(open.key), holder.current, open.code, ui, { fill: true });
|
|
160
|
+
P().start(frameKey(open.key), holder.current, open.code, ui, anyway === true || open.anyway ? { fill: true, anyway: true } : { fill: true });
|
|
169
161
|
};
|
|
170
162
|
const stop = () => { if (open) P().stop(frameKey(open.key), "stopped", ""); };
|
|
171
163
|
// A new block in the panel: what ran before is gone (openPanel stopped it); the card's click starts it here — or,
|
|
@@ -233,7 +225,8 @@ export function PreviewPanel({ name, split, msgs, subscribe, keyOf, reveal, down
|
|
|
233
225
|
if (newer.code != null) { openPanel(newer); return; }
|
|
234
226
|
const was = open && open.key, mine = reading.current = newer;
|
|
235
227
|
setNewerNote(t("chat.pvLoading"));
|
|
236
|
-
|
|
228
|
+
// A click on the newer version reads it whatever its size (up to the 50 MB ceiling); over 8 MB the panel then asks.
|
|
229
|
+
const r = await loadHtmlAttachment(newer.att, undefined, { anyway: true });
|
|
237
230
|
if (reading.current !== mine) return;
|
|
238
231
|
reading.current = null;
|
|
239
232
|
const now = panelStore.get().open;
|
|
@@ -245,9 +238,6 @@ export function PreviewPanel({ name, split, msgs, subscribe, keyOf, reveal, down
|
|
|
245
238
|
// Esc in the panel closes it (and is not the chat's "stop the reply").
|
|
246
239
|
const onPanelKey = (e) => { if (e.key === "Escape" && !e.isComposing) { e.preventDefault(); closePanel(); } };
|
|
247
240
|
|
|
248
|
-
if (trial) return html`<${EffectTrialPanel} binding=${trial.binding} title=${trial.handle.reply.offer.title}
|
|
249
|
-
current=${trial.handle.current} ownerCss=${trial.ownerCss} onStage=${trial.onStage}
|
|
250
|
-
onStop=${trial.handle.stop} onClose=${() => { if (panelStore.get().open === trial) closePanel(); }} />`;
|
|
251
241
|
if (!open) return null;
|
|
252
242
|
const going = run.state === "starting" || run.state === "running";
|
|
253
243
|
const a = P().availability();
|
|
@@ -282,9 +272,10 @@ export function PreviewPanel({ name, split, msgs, subscribe, keyOf, reveal, down
|
|
|
282
272
|
<button type="button" class=${`btn btn-sm${view === "code" ? " on" : ""}`} aria-pressed=${view === "code" ? "true" : "false"} onClick=${() => setView("code")}><${Icon} name="code" size=${14} />${t("chat.pvCode")}</button>
|
|
283
273
|
</div>
|
|
284
274
|
<span class="grow"></span>
|
|
285
|
-
${going ? html`<button type="button" class="btn btn-sm pv-panel-reload" title=${t("chat.pvReload")} onClick=${() => { stop(); start(); }}><${Icon} name="restart" size=${14} /><span class="lbl">${t("chat.pvReload")}</span></button>
|
|
275
|
+
${going ? html`<button type="button" class="btn btn-sm pv-panel-reload" title=${t("chat.pvReload")} onClick=${() => { stop(); start(true); }}><${Icon} name="restart" size=${14} /><span class="lbl">${t("chat.pvReload")}</span></button>
|
|
286
276
|
<button type="button" class="btn btn-sm pv-panel-stop" title=${t("chat.pvStop")} onClick=${stop}><${Icon} name="stop" size=${14} /><span class="lbl">${t("chat.pvStop")}</span></button>`
|
|
287
|
-
: html`<button type="button" class="btn btn-sm btn-primary pv-panel-
|
|
277
|
+
: run.state === "large" ? html`<button type="button" class="btn btn-sm btn-primary pv-panel-anyway" title=${t("chat.pvAnyway")} disabled=${!a.ok} onClick=${() => start(true)}><${Icon} name="play" size=${14} /><span class="lbl">${t("chat.pvAnyway")}</span></button>`
|
|
278
|
+
: html`<button type="button" class="btn btn-sm btn-primary pv-panel-run" title=${t("chat.pvPreview")} disabled=${!a.ok} onClick=${() => start()}><${Icon} name="play" size=${14} /><span class="lbl">${t("chat.pvPreview")}</span></button>`}
|
|
288
279
|
<button type="button" class="btn btn-sm pv-panel-dl" title=${t("chat.pvDownload")} onClick=${() => download(open.code, fileName)}><${Icon} name="download" size=${14} /><span class="lbl">${t("chat.pvDownload")}</span></button>
|
|
289
280
|
</div>
|
|
290
281
|
${newer && newer.key !== open.key ? html`<div class="pv-newer" role="status"><span>${t("chat.pvNewer", timeOf(newer.ts))}</span>
|
package/dist/ui/preview.js
CHANGED
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
})(typeof globalThis !== "undefined" ? globalThis : this, function (root) {
|
|
21
21
|
"use strict";
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
// #2863: askBytes — above it the person is asked first ("Preview anyway?"); maxBytes — above it only a download (the
|
|
24
|
+
// frame's own ceiling, web-preview.ts PREVIEW_HTML_MAX_BYTES; html-attachment.js reads with the same two).
|
|
25
|
+
var LIMITS = { askBytes: 8 * 1024 * 1024, maxBytes: 50 * 1024 * 1024, minHeight: 40, maxHeight: 4000, readyMs: 3000, watchdogMs: 10000, resizePerSec: 10, minDelta: 2, growthFreeze: 5, growthWindowMs: 2000 };
|
|
24
26
|
var OPT_IN_KEY = "agend_html_preview"; // localStorage: "on" — this device allows previews
|
|
25
27
|
var NEVER_KEY = "agend_html_preview_never"; // sessionStorage: "1" — never preview on this device (this session)
|
|
26
28
|
// #1568: the banner was acknowledged ("I understand"). localStorage: the sign-in's session handle — hidden while the
|
|
@@ -54,6 +56,8 @@
|
|
|
54
56
|
function now() { return root.performance && typeof root.performance.now === "function" ? root.performance.now() : Date.now(); }
|
|
55
57
|
function store(kind) { try { return root[kind] || null; } catch (e) { return null; } }
|
|
56
58
|
function utf8Bytes(s) { try { return new root.TextEncoder().encode(s).length; } catch (e) { return String(s).length * 3; } }
|
|
59
|
+
/** #2863: "12 MB" / "8.4 MB" (1 MB = 1024 × 1024 bytes, as the caps are; html-attachment.js sizeLabel). */
|
|
60
|
+
function sizeLabel(bytes) { var mb = bytes / (1024 * 1024); return (mb >= 10 ? Math.round(mb) : Math.round(mb * 10) / 10) + " MB"; }
|
|
57
61
|
function randomCh() {
|
|
58
62
|
var b = new Uint8Array(16);
|
|
59
63
|
root.crypto.getRandomValues(b);
|
|
@@ -229,7 +233,8 @@
|
|
|
229
233
|
|
|
230
234
|
/**
|
|
231
235
|
* Run `html` in `holder` (a node of the card) for `key`. `ui` is told what happened: ui.state(name, reason) with
|
|
232
|
-
* "starting" | "running" | "stopped" | "unavailable"
|
|
236
|
+
* "starting" | "running" | "stopped" | "unavailable" | "large" (#2863: over LIMITS.askBytes — nothing runs until it is
|
|
237
|
+
* asked again with opts.anyway, the person's "Preview anyway"). One preview at a time: any other running one stops first.
|
|
233
238
|
* opts.fill (#1481, the side panel): the frame fills its holder — the same frame from mountPreview, only sized by the
|
|
234
239
|
* panel instead of by the frame's height messages, which it then ignores.
|
|
235
240
|
*/
|
|
@@ -239,7 +244,9 @@
|
|
|
239
244
|
function start(key, holder, html, ui, opts) {
|
|
240
245
|
var a = availability();
|
|
241
246
|
if (!a.ok) { ui.state("unavailable", a.reason); return false; }
|
|
242
|
-
|
|
247
|
+
var bytes = typeof html === "string" ? utf8Bytes(html) : Infinity;
|
|
248
|
+
if (bytes > LIMITS.maxBytes) { ui.state("unavailable", say("pvHtmlOver", "This HTML is over 50 MB; it is not previewed. Download it to open it.")); return false; }
|
|
249
|
+
if (bytes > LIMITS.askBytes && !(opts && opts.anyway)) { ui.state("large", say("pvLarge", "This file is large ({0}). Preview anyway?", sizeLabel(bytes))); return false; }
|
|
243
250
|
stopAll("another");
|
|
244
251
|
starts++;
|
|
245
252
|
var doc = holder.ownerDocument;
|