@songsid/agend 2.2.0 → 2.3.0-beta.2
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.js +248 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +2 -0
- package/dist/agent-endpoint.js +25 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.d.ts +35 -0
- package/dist/agent-file-read.js +144 -0
- package/dist/agent-file-read.js.map +1 -0
- package/dist/agent-whats-new.js +8 -0
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/atomic-write.js +9 -1
- package/dist/atomic-write.js.map +1 -1
- package/dist/auth-api.js +17 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/antigravity.js +3 -3
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.js +5 -1
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/launch-effort.js +1 -1
- package/dist/backend/launch-effort.js.map +1 -1
- package/dist/backend/muse.js +10 -2
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +8 -0
- package/dist/backend/types.js +10 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/cache-analysis.d.ts +47 -1
- package/dist/cache-analysis.js +68 -8
- package/dist/cache-analysis.js.map +1 -1
- package/dist/cache-report-tool.d.ts +17 -0
- package/dist/cache-report-tool.js +40 -0
- package/dist/cache-report-tool.js.map +1 -0
- package/dist/cache-service.d.ts +15 -0
- package/dist/cache-service.js +26 -1
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +2 -0
- package/dist/channel/adapters/discord.js +17 -2
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +8 -1
- package/dist/channel/adapters/telegram.js +74 -4
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +36 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/types.d.ts +15 -0
- package/dist/cli.js +7 -3
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +22 -2
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.js +244 -20
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-context.d.ts +5 -0
- package/dist/fleet-manager.d.ts +92 -0
- package/dist/fleet-manager.js +410 -29
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +7 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +367 -0
- package/dist/instance-avatar.js +3 -0
- package/dist/instance-avatar.js.map +1 -1
- package/dist/instance-config-impact.js +3 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.js +9 -4
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/needs-you-hub.d.ts +4 -0
- package/dist/needs-you-hub.js +31 -6
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +1 -13
- package/dist/needs-you.js +3 -37
- package/dist/needs-you.js.map +1 -1
- package/dist/outbound-handlers.d.ts +7 -0
- package/dist/outbound-handlers.js +65 -10
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +74 -0
- package/dist/outbound-schemas.js +56 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +14 -0
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/repo-worktree-guard.d.ts +36 -0
- package/dist/repo-worktree-guard.js +143 -0
- package/dist/repo-worktree-guard.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +70 -1
- package/dist/scheduler/scheduler.js +256 -17
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/service-installer.d.ts +2 -1
- package/dist/service-installer.js +18 -3
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.js +74 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +8 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +16 -2
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +5 -0
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/tool-permissions.d.ts +5 -0
- package/dist/tool-permissions.js +61 -2
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +10 -6
- package/dist/topic-commands.js +21 -17
- package/dist/topic-commands.js.map +1 -1
- package/dist/topic-link.d.ts +128 -0
- package/dist/topic-link.js +269 -0
- package/dist/topic-link.js.map +1 -0
- package/dist/types.d.ts +13 -0
- package/dist/ui/app-effects.js +187 -0
- package/dist/ui/chat-effect-posters.js +42 -0
- package/dist/ui/chat-effects.js +72 -0
- package/dist/ui/chat-live-replies.js +58 -0
- package/dist/ui/chat-render.js +4 -4
- package/dist/ui/chat-store.js +9 -3
- package/dist/ui/chat-strings.js +15 -0
- package/dist/ui/chat-thread.js +47 -21
- package/dist/ui/chat-tour.js +1 -1
- package/dist/ui/effect-trial-controller.js +119 -0
- package/dist/ui/effect-trial-offer.js +31 -0
- package/dist/ui/effect-trial-panel.js +37 -0
- package/dist/ui/fleet-cache.js +23 -1
- package/dist/ui/image-lightbox.js +1 -1
- package/dist/ui/panel-chat.js +85 -17
- package/dist/ui/panel-details.js +18 -5
- package/dist/ui/panel-fleet.js +3 -3
- package/dist/ui/panel-log.js +6 -6
- package/dist/ui/panel-needs.js +3 -3
- package/dist/ui/panel-settings.js +8 -5
- package/dist/ui/preview-panel.js +25 -9
- package/dist/ui/settings-appearance.js +325 -0
- package/dist/ui/settings-effects.js +201 -0
- package/dist/ui/settings-mods.js +257 -0
- package/dist/ui/settings-owner-effects.js +80 -0
- package/dist/ui/settings-strings.js +245 -0
- package/dist/ui/shared/agend-auth.js +27 -3
- package/dist/ui/shared/agent-widget.js +248 -0
- package/dist/ui/shared/app-i18n.js +2 -0
- package/dist/ui/shared/app-prefs.js +30 -10
- package/dist/ui/shared/app-route.js +23 -1
- package/dist/ui/shared/app-session.js +1 -1
- package/dist/ui/shared/app-shell.js +29 -16
- package/dist/ui/shared/app-store.js +2 -0
- package/dist/ui/shared/app-stream.js +12 -7
- package/dist/ui/shared/app-themes.js +188 -0
- package/dist/ui/shared/app.css +225 -3
- package/dist/ui/shared/app.js +6 -0
- package/dist/ui/shared/card-strings.js +28 -0
- package/dist/ui/shared/effect-app-runtime.js +248 -0
- package/dist/ui/shared/effect-baseline.js +64 -0
- package/dist/ui/shared/effect-chat-runtime.js +154 -0
- package/dist/ui/shared/effect-cursor.js +125 -0
- package/dist/ui/shared/effect-frame.js +127 -0
- package/dist/ui/shared/effect-geometry.js +122 -0
- package/dist/ui/shared/effect-host-style.js +10 -0
- package/dist/ui/shared/effect-owner-regions.js +74 -0
- package/dist/ui/shared/effect-owner-source.js +115 -0
- package/dist/ui/shared/effect-policy.js +137 -0
- package/dist/ui/shared/effect-poster-reader.js +98 -0
- package/dist/ui/shared/effect-posters.js +117 -0
- package/dist/ui/shared/effect-profiler.js +162 -0
- package/dist/ui/shared/effect-profiles.js +65 -0
- package/dist/ui/shared/effect-registry.js +496 -0
- package/dist/ui/shared/effect-runtime-loader.js +234 -0
- package/dist/ui/shared/effect-safety.js +153 -0
- package/dist/ui/shared/effect-stock-snapshot.js +34 -0
- package/dist/ui/shared/effect-stock-trust.js +165 -0
- package/dist/ui/shared/effect-trial-source.js +103 -0
- package/dist/ui/shared/effect-trial-stage.js +147 -0
- package/dist/ui/shared/header-tools.js +1 -1
- package/dist/ui/shared/identity-card.js +202 -0
- package/dist/ui/shared/identity-color.js +261 -0
- package/dist/ui/shared/instance-nav.js +37 -19
- package/dist/ui/shared/owner-css.js +221 -0
- package/dist/ui/shared/owner-effect-prefs.js +117 -0
- package/dist/ui/shared/panel-view.js +81 -8
- package/dist/ui/shared/signin.js +2 -0
- package/dist/ui/shared/ui-dialog.js +1 -1
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/shared/ui-menu.js +1 -1
- package/dist/ui/shared/ui-toast.js +1 -1
- package/dist/ui/shared/usage-dialog.js +14 -2
- package/dist/ui/shared/view-strings.js +36 -2
- package/dist/ui/signin.html +1 -0
- package/dist/usage/providers.d.ts +3 -0
- package/dist/usage/providers.js +4 -3
- package/dist/usage/providers.js.map +1 -1
- package/dist/view-api.d.ts +29 -0
- package/dist/view-api.js +188 -8
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +42 -0
- package/dist/web-api.js +898 -36
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-limits.d.ts +82 -0
- package/dist/web-appearance-limits.js +83 -0
- package/dist/web-appearance-limits.js.map +1 -0
- package/dist/web-appearance-policy.d.ts +66 -0
- package/dist/web-appearance-policy.js +94 -0
- package/dist/web-appearance-policy.js.map +1 -0
- package/dist/web-appearance-tools.d.ts +50 -0
- package/dist/web-appearance-tools.js +105 -0
- package/dist/web-appearance-tools.js.map +1 -0
- package/dist/web-appearance-view.d.ts +99 -0
- package/dist/web-appearance-view.js +163 -0
- package/dist/web-appearance-view.js.map +1 -0
- package/dist/web-appearance.d.ts +79 -0
- package/dist/web-appearance.js +298 -0
- package/dist/web-appearance.js.map +1 -0
- package/dist/web-asset-parts.d.ts +93 -0
- package/dist/web-asset-parts.js +133 -0
- package/dist/web-asset-parts.js.map +1 -0
- package/dist/web-auth.js +17 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-card.d.ts +96 -0
- package/dist/web-card.js +232 -0
- package/dist/web-card.js.map +1 -0
- package/dist/web-chat-history.d.ts +13 -0
- package/dist/web-chat-history.js +23 -2
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-effect-bundle.d.ts +28 -0
- package/dist/web-effect-bundle.js +126 -0
- package/dist/web-effect-bundle.js.map +1 -0
- package/dist/web-effect-canonical.d.ts +8 -0
- package/dist/web-effect-canonical.js +27 -0
- package/dist/web-effect-canonical.js.map +1 -0
- package/dist/web-effect-library.d.ts +114 -0
- package/dist/web-effect-library.js +530 -0
- package/dist/web-effect-library.js.map +1 -0
- package/dist/web-effect-poster.d.ts +8 -0
- package/dist/web-effect-poster.js +52 -0
- package/dist/web-effect-poster.js.map +1 -0
- package/dist/web-effect-profiles.d.ts +118 -0
- package/dist/web-effect-profiles.js +180 -0
- package/dist/web-effect-profiles.js.map +1 -0
- package/dist/web-effect-service.d.ts +86 -0
- package/dist/web-effect-service.js +314 -0
- package/dist/web-effect-service.js.map +1 -0
- package/dist/web-effect-tools.d.ts +47 -0
- package/dist/web-effect-tools.js +129 -0
- package/dist/web-effect-tools.js.map +1 -0
- package/dist/web-effect-trials.d.ts +86 -0
- package/dist/web-effect-trials.js +204 -0
- package/dist/web-effect-trials.js.map +1 -0
- package/dist/web-effect-upload.d.ts +27 -0
- package/dist/web-effect-upload.js +185 -0
- package/dist/web-effect-upload.js.map +1 -0
- package/dist/web-effect-worker.d.ts +8 -0
- package/dist/web-effect-worker.js +170 -0
- package/dist/web-effect-worker.js.map +1 -0
- package/dist/web-effect.d.ts +103 -0
- package/dist/web-effect.js +153 -0
- package/dist/web-effect.js.map +1 -0
- package/dist/web-identity-color.d.ts +87 -0
- package/dist/web-identity-color.js +209 -0
- package/dist/web-identity-color.js.map +1 -0
- package/dist/web-identity-palette.d.ts +113 -0
- package/dist/web-identity-palette.js +84 -0
- package/dist/web-identity-palette.js.map +1 -0
- package/dist/web-image-meta.d.ts +25 -0
- package/dist/web-image-meta.js +200 -0
- package/dist/web-image-meta.js.map +1 -0
- package/dist/web-mod-agent.d.ts +50 -0
- package/dist/web-mod-agent.js +205 -0
- package/dist/web-mod-agent.js.map +1 -0
- package/dist/web-mod-bundle.d.ts +149 -0
- package/dist/web-mod-bundle.js +470 -0
- package/dist/web-mod-bundle.js.map +1 -0
- package/dist/web-mod-export.d.ts +59 -0
- package/dist/web-mod-export.js +79 -0
- package/dist/web-mod-export.js.map +1 -0
- package/dist/web-mod-store.d.ts +283 -0
- package/dist/web-mod-store.js +625 -0
- package/dist/web-mod-store.js.map +1 -0
- package/dist/web-org.d.ts +3 -3
- package/dist/web-org.js +11 -17
- package/dist/web-org.js.map +1 -1
- package/dist/web-owner-css-store.d.ts +180 -0
- package/dist/web-owner-css-store.js +384 -0
- package/dist/web-owner-css-store.js.map +1 -0
- package/dist/web-owner-css.d.ts +58 -0
- package/dist/web-owner-css.js +552 -0
- package/dist/web-owner-css.js.map +1 -0
- package/dist/web-prefs.d.ts +48 -0
- package/dist/web-prefs.js +209 -33
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +9 -1
- package/dist/web-preview.js +24 -7
- package/dist/web-preview.js.map +1 -1
- package/dist/web-settings-patch.d.ts +18 -0
- package/dist/web-settings-patch.js +51 -0
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-shell-routes.d.ts +1 -1
- package/dist/web-shell-routes.js +1 -1
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-theme-store.d.ts +218 -0
- package/dist/web-theme-store.js +516 -0
- package/dist/web-theme-store.js.map +1 -0
- package/dist/web-theme.d.ts +121 -0
- package/dist/web-theme.js +407 -0
- package/dist/web-theme.js.map +1 -0
- package/dist/web-widget.d.ts +72 -0
- package/dist/web-widget.js +245 -0
- package/dist/web-widget.js.map +1 -0
- package/package.json +1 -1
- package/templates/systemd.service.ejs +8 -0
package/dist/ui/fleet-cache.js
CHANGED
|
@@ -31,6 +31,11 @@ register("cache", {
|
|
|
31
31
|
shared: "shares its working directory with {0}; their transcripts cannot be told apart",
|
|
32
32
|
empty: "No instances", emptyHint: "Instances show here once the fleet has them.",
|
|
33
33
|
b0: "≤5m", b1: "≤30m", b2: "≤1h", b3: "≤2h", b4: "≤6h", b5: "≤24h", b6: ">24h", tokensUnit: "input-token equivalents",
|
|
34
|
+
writeMix: "Cache writes 5 min / 1 h", effectiveTtl: "Behaves as", gapMiss: "{0} of {1} 5–60 min gaps rewrote it", gapMissNone: "no 5–60 min gaps yet",
|
|
35
|
+
effMeasured: "{0} (measured)", effLabel: "{0} (as written)", effUnproven: "not known yet",
|
|
36
|
+
fleetSwitch: "Switch to 1-hour cache",
|
|
37
|
+
switchTo1h: "Its 5-minute cache really expires: {0} of {1} gaps of 5–60 minutes rewrote it. Set prompt_cache_ttl: \"1h\" for this instance (backend_options.claude-code) rather than keep-warm.",
|
|
38
|
+
unproven: "Written with a 5-minute TTL, but only {0} gaps of 5–60 minutes so far — too few to tell how long it really lasts. The keep-warm figures assume 5 minutes.",
|
|
34
39
|
},
|
|
35
40
|
"zh-TW": {
|
|
36
41
|
title: "快取", w24h: "24 小時", w7d: "7 天", w30d: "30 天", window: "期間",
|
|
@@ -53,6 +58,11 @@ register("cache", {
|
|
|
53
58
|
shared: "與 {0} 共用工作目錄,無法分辨各自的 transcript",
|
|
54
59
|
empty: "沒有 instance", emptyHint: "fleet 有 instance 後會顯示在這裡。",
|
|
55
60
|
b0: "≤5分", b1: "≤30分", b2: "≤1時", b3: "≤2時", b4: "≤6時", b5: "≤24時", b6: ">24時", tokensUnit: "輸入 token 當量",
|
|
61
|
+
writeMix: "快取寫入 5 分 / 1 時", effectiveTtl: "實際存活", gapMiss: "{1} 個 5–60 分鐘空檔中有 {0} 個重寫", gapMissNone: "還沒有 5–60 分鐘的空檔",
|
|
62
|
+
effMeasured: "{0}(實測)", effLabel: "{0}(依寫入標記)", effUnproven: "尚無法判斷",
|
|
63
|
+
fleetSwitch: "建議改用 1 小時快取",
|
|
64
|
+
switchTo1h: "它的 5 分鐘快取確實會過期:{1} 個 5–60 分鐘空檔中有 {0} 個重寫。請為這個 instance 設定 prompt_cache_ttl: \"1h\"(backend_options.claude-code),而不是保溫。",
|
|
65
|
+
unproven: "以 5 分鐘 TTL 寫入,但目前只有 {0} 個 5–60 分鐘空檔,太少,無法判斷實際存活多久。保溫的數字以 5 分鐘估算。",
|
|
56
66
|
},
|
|
57
67
|
});
|
|
58
68
|
const tc = (k, ...v) => t(`cache.${k}`, ...v);
|
|
@@ -79,6 +89,8 @@ const pct = (x) => `${x >= 0.1 ? Math.round(x * 100) : (x * 100).toFixed(1)}%`;
|
|
|
79
89
|
export function reasonText(a) {
|
|
80
90
|
const r = a.recommendation, ttl = ttlText(a.ttlSec);
|
|
81
91
|
if (r.reason === "quiet") return tc("quiet");
|
|
92
|
+
if (r.reason === "switch_to_1h") return tc("switchTo1h", a.effectiveTtl.misses, a.effectiveTtl.sample);
|
|
93
|
+
if (r.reason === "insufficient_evidence") return tc("unproven", a.effectiveTtl.sample);
|
|
82
94
|
if (r.reason === "never_expired") {
|
|
83
95
|
if (a.sim.pastTtl) return tc("survived", a.sim.pastTtl, ttl);
|
|
84
96
|
return a.sim.tailPings ? tc("onlyIdle", a.sim.tailPings.toLocaleString("en-US")) : tc("neverExpired", ttl);
|
|
@@ -107,6 +119,12 @@ function Histogram({ buckets, ttlSec }) {
|
|
|
107
119
|
</svg>`;
|
|
108
120
|
}
|
|
109
121
|
|
|
122
|
+
/** #2010: what the TTL behaves as — measured, as written, or not known yet. */
|
|
123
|
+
export function effectiveText(e) {
|
|
124
|
+
if (e.ttlSec === null) return tc("effUnproven");
|
|
125
|
+
return e.basis === "measured" ? tc("effMeasured", ttlText(e.ttlSec)) : tc("effLabel", ttlText(e.ttlSec));
|
|
126
|
+
}
|
|
127
|
+
|
|
110
128
|
function Metric({ label, value, sub }) {
|
|
111
129
|
return html`<div class="ch-metric"><dt>${label}</dt><dd>${value}${sub ? html`<span class="ch-sub">${sub}</span>` : null}</dd></div>`;
|
|
112
130
|
}
|
|
@@ -130,8 +148,11 @@ function InstanceCard({ r }) {
|
|
|
130
148
|
<${Metric} label=${tc("pings")} value=${(s.pings + s.tailPings).toLocaleString("en-US")} sub=${s.tailPings ? tc("pingsAfter", s.tailPings) : null} />
|
|
131
149
|
<${Metric} label=${tc("pingCost")} value=${$(s.pingCost + s.tailCost)} />
|
|
132
150
|
<${Metric} label=${tc("net")} value=${a.priced ? `${s.net >= 0 ? "+" : "−"}${money(Math.abs(s.net))}` : "—"} />
|
|
151
|
+
${a.writeMix["5m"] + a.writeMix["1h"] > 0 ? html`<${Metric} label=${tc("writeMix")} value=${`${tokens(a.writeMix["5m"])} / ${tokens(a.writeMix["1h"])}`} />` : null}
|
|
152
|
+
${r.backend === "claude-code" ? html`<${Metric} label=${tc("effectiveTtl")} value=${effectiveText(a.effectiveTtl)}
|
|
153
|
+
sub=${a.effectiveTtl.sample ? tc("gapMiss", a.effectiveTtl.misses, a.effectiveTtl.sample) : tc("gapMissNone")} />` : null}
|
|
133
154
|
</dl>
|
|
134
|
-
<${Histogram} buckets=${a.gapBuckets} ttlSec=${a.ttlSec} />
|
|
155
|
+
<${Histogram} buckets=${a.gapBuckets} ttlSec=${a.effectiveTtl.basis === "measured" && a.effectiveTtl.ttlSec !== null ? a.effectiveTtl.ttlSec : a.ttlSec} />
|
|
135
156
|
</article>`;
|
|
136
157
|
}
|
|
137
158
|
|
|
@@ -154,6 +175,7 @@ export function CacheReport({ report, win, setWin, refresh }) {
|
|
|
154
175
|
<${Metric} label=${tc("fleetExpiry")} value=${money(f.expiryCost)} />
|
|
155
176
|
<${Metric} label=${tc("fleetSaving")} value=${money(f.saving)} />
|
|
156
177
|
<${Metric} label=${tc("fleetRecommended")} value=${tc("ofN", f.recommended, f.analysed)} />
|
|
178
|
+
${f.switchTo1h ? html`<${Metric} label=${tc("fleetSwitch")} value=${tc("ofN", f.switchTo1h, f.analysed)} />` : null}
|
|
157
179
|
</section>` : html`<${Empty} icon="chart" title=${tc("empty")} hint=${tc("emptyHint")} />`}
|
|
158
180
|
<div class="ch-notes note">
|
|
159
181
|
<p>${tc("pricesNote", report.pricesChecked)} ${tc("quotaNote")}</p>
|
|
@@ -90,7 +90,7 @@ function ImageLightbox({ items, index, opener, onClose }) {
|
|
|
90
90
|
return () => { d.removeEventListener("cancel", onCancel); d.removeEventListener("close", onNativeClose); d.removeEventListener("click", onClick); d.removeEventListener("keydown", onKey); };
|
|
91
91
|
}, [onClose, many, items.length]);
|
|
92
92
|
const label = item.name ? t("chat.lbLabel", item.name) : t("chat.lbLabelNoName");
|
|
93
|
-
return html`<dialog ref=${ref} class="lb" role="dialog" aria-modal="true" aria-label=${label}>
|
|
93
|
+
return html`<dialog data-trust="lightbox" ref=${ref} class="lb" role="dialog" aria-modal="true" aria-label=${label}>
|
|
94
94
|
<div class="lb-bar">
|
|
95
95
|
<span class="lb-name" title=${item.name}>${item.name}</span>
|
|
96
96
|
${many ? html`<span class="lb-count">${t("chat.lbCount", at + 1, items.length)}</span>` : null}
|
package/dist/ui/panel-chat.js
CHANGED
|
@@ -12,10 +12,10 @@ import { appStore, useStore } from "/assets/app-store.js";
|
|
|
12
12
|
import { useLease } from "/assets/app-ctx.js";
|
|
13
13
|
import { namesOf, namesStore } from "/assets/instance-nav.js";
|
|
14
14
|
import { PanelHeader, onPanelKey, setTitle, statusClass, statusLabel, openDrawer, requestNewInstance } from "/assets/app-shell.js";
|
|
15
|
-
import { detailsPath } from "/assets/app-route.js";
|
|
15
|
+
import { detailsPath, textLinkSegments } from "/assets/app-route.js";
|
|
16
16
|
import { InstanceSwitch } from "/assets/instance-switch.js";
|
|
17
17
|
import { HeaderTools } from "/assets/header-tools.js";
|
|
18
|
-
import { navigate } from "/assets/app-nav.js";
|
|
18
|
+
import { navigate, navStore } from "/assets/app-nav.js";
|
|
19
19
|
import { Menu } from "/assets/ui-menu.js";
|
|
20
20
|
import { Dialog } from "/assets/ui-dialog.js";
|
|
21
21
|
import { Empty, Skeleton } from "/assets/ui-states.js";
|
|
@@ -23,6 +23,26 @@ import { Icon } from "/assets/ui-icons.js";
|
|
|
23
23
|
import { toast } from "/assets/ui-toast.js";
|
|
24
24
|
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
25
25
|
import { savePref } from "/assets/app-prefs.js";
|
|
26
|
+
import { applyTheme, resolvedMode, themeFor, themeImageUrl, themeMenuItems, themesStore } from "/assets/app-themes.js";
|
|
27
|
+
import { colorMenuItem } from "/assets/identity-color.js";
|
|
28
|
+
import { IdentityCard, useCard } from "/assets/identity-card.js";
|
|
29
|
+
import { AgentWidget, instanceBinds, widgetFor } from "/assets/agent-widget.js";
|
|
30
|
+
|
|
31
|
+
// #1629 r4 §1: the identity card folded into the chat's header — folded by default; open or not is this device's choice.
|
|
32
|
+
const CHAT_CARD_KEY = "agend_chat_card";
|
|
33
|
+
const chatCardOpen = () => { try { return localStorage.getItem(CHAT_CARD_KEY) === "open"; } catch { return false; } };
|
|
34
|
+
function ChatCard({ name, card }) {
|
|
35
|
+
const [open, setOpen] = useState(chatCardOpen);
|
|
36
|
+
if (!card) return null;
|
|
37
|
+
const toggle = () => { const next = !open; setOpen(next); try { if (next) localStorage.setItem(CHAT_CARD_KEY, "open"); else localStorage.removeItem(CHAT_CARD_KEY); } catch { /* this page only */ } };
|
|
38
|
+
return html`<div data-trust="card" class=${`chat-card${open ? " open" : ""}`}>
|
|
39
|
+
<button type="button" class="chat-card-toggle" aria-expanded=${open ? "true" : "false"} onClick=${toggle}>
|
|
40
|
+
<${Icon} name="chevron" size=${14} cls=${open ? "caret" : "caret flipped"} /><span>${t(open ? "card.hide" : "card.show")}</span>
|
|
41
|
+
${!open && card.tagline ? html`<span class="chat-card-peek">${card.tagline.text}</span>` : null}
|
|
42
|
+
</button>
|
|
43
|
+
${open ? html`<${IdentityCard} name=${name} card=${card} compact=${true} />` : null}
|
|
44
|
+
</div>`;
|
|
45
|
+
}
|
|
26
46
|
import "./chat-strings.js";
|
|
27
47
|
import { confirmedWrite } from "./settings-confirm.js";
|
|
28
48
|
import { createChatStore } from "./chat-store.js";
|
|
@@ -34,6 +54,9 @@ import { PreviewPanel, claimPanel, openPanel, panelClaims, panelStore, panelVers
|
|
|
34
54
|
import { needsArgument, paletteFor, parseCommandLine } from "./chat-commands.js";
|
|
35
55
|
import { installTour, refreshTourSpot } from "./chat-tour.js";
|
|
36
56
|
import { LightboxHost, openLightbox } from "./image-lightbox.js";
|
|
57
|
+
import { ChatEffectPosters } from "/ui/js/chat-effect-posters.js";
|
|
58
|
+
import { ChatEffects, chatEffectsMenuAvailable } from "./chat-effects.js";
|
|
59
|
+
import { startAppEffects, getAppEffects } from "./app-effects.js";
|
|
37
60
|
|
|
38
61
|
const R = () => globalThis.AgendChatRender;
|
|
39
62
|
const P = () => globalThis.AgendPreview;
|
|
@@ -93,6 +116,7 @@ export function boot({ stream, boot: bootData, deps = {} }) {
|
|
|
93
116
|
}
|
|
94
117
|
// #1604: the preview opt-in and Tour live in Settings → This device now (and on each preview card's menu).
|
|
95
118
|
installTour();
|
|
119
|
+
startAppEffects({ store });
|
|
96
120
|
return store;
|
|
97
121
|
}
|
|
98
122
|
|
|
@@ -171,11 +195,40 @@ function NotFound({ name }) {
|
|
|
171
195
|
action=${html`<button type="button" class="btn only-narrow" onClick=${openDrawer}>${t("chat.backToList")}</button>`} /></div></div>`;
|
|
172
196
|
}
|
|
173
197
|
|
|
198
|
+
/**
|
|
199
|
+
* #1629: the mode the page is drawn in, followed live (the chosen theme, or the system's for "system"), so an agent's
|
|
200
|
+
* light and dark palettes swap with it.
|
|
201
|
+
*/
|
|
202
|
+
function useResolvedMode(lease) {
|
|
203
|
+
const [mode, setMode] = useState(resolvedMode);
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
const T = globalThis.AgendTheme;
|
|
206
|
+
const update = () => setMode(resolvedMode());
|
|
207
|
+
const off = T && typeof T.onChange === "function" ? T.onChange(update) : null;
|
|
208
|
+
let mq = null;
|
|
209
|
+
try { mq = globalThis.matchMedia ? globalThis.matchMedia("(prefers-color-scheme: light)") : null; } catch { mq = null; }
|
|
210
|
+
if (mq && typeof mq.addEventListener === "function") lease.on(mq, "change", update);
|
|
211
|
+
return () => { if (typeof off === "function") off(); };
|
|
212
|
+
}, [lease]);
|
|
213
|
+
return mode;
|
|
214
|
+
}
|
|
215
|
+
|
|
174
216
|
function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
175
217
|
const names = useStore(namesStore); // #1722: which name leads (the title)
|
|
218
|
+
// #1629: this agent's theme, on this surface only; redrawn when the themes, the viewer's switch or the mode move.
|
|
219
|
+
useStore(themesStore);
|
|
220
|
+
const mode = useResolvedMode(lease);
|
|
221
|
+
const theme = themeFor(name);
|
|
222
|
+
const themeKey = JSON.stringify(theme);
|
|
223
|
+
const card = useCard(name, inst.card_v);
|
|
176
224
|
const [dialog, setDialog] = useState(null); // "delete" | null
|
|
177
225
|
const [wrap, setWrap] = useState(codeWrap);
|
|
178
|
-
const view = useRef(null), split = useRef(null), thread = useRef(null);
|
|
226
|
+
const view = useRef(null), split = useRef(null), thread = useRef(null), bg = useRef(null), effectSurface = useRef(null);
|
|
227
|
+
useLayoutEffect(() => { applyTheme(view.current, theme, mode); }, [themeKey, mode]);
|
|
228
|
+
useLayoutEffect(() => {
|
|
229
|
+
const img = bg.current;
|
|
230
|
+
if (img && img.style && theme && theme.background) img.style.setProperty("--th-bg-opacity", String(theme.background.opacity));
|
|
231
|
+
}, [themeKey]);
|
|
179
232
|
useEffect(() => { wrapListeners.add(setWrap); return () => wrapListeners.delete(setWrap); }, []);
|
|
180
233
|
// First visit to this instance's chat on this page: its history (a navigation — it counts as use). Back again: nothing.
|
|
181
234
|
useEffect(() => { store.openHistory(name, lease); }, [name]);
|
|
@@ -217,6 +270,11 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
217
270
|
running ? null : { key: "start", label: t("chat.start"), icon: "play", onSelect: () => action("start") },
|
|
218
271
|
running ? { key: "restart", label: t("chat.restart"), icon: "restart", onSelect: () => action("restart") } : null,
|
|
219
272
|
running ? { key: "stop", label: t("chat.stopInstance"), icon: "stop", onSelect: () => action("stop") } : null,
|
|
273
|
+
// #1958: its identity colour and where it shows (the public link too, as profile edits).
|
|
274
|
+
colorMenuItem(name, inst),
|
|
275
|
+
// #1629: a person's control over this agent's theme — not over the public link (it only views themes).
|
|
276
|
+
...themeMenuItems(name),
|
|
277
|
+
chatEffectsMenuAvailable() ? { key: "effects", label: t("chatFx.title"), onSelect: () => setDialog("effects") } : null,
|
|
220
278
|
{ key: "delete", label: t("chat.delete"), icon: "trash", danger: true, onSelect: () => setDialog("delete") },
|
|
221
279
|
];
|
|
222
280
|
const cls = statusClass(inst, exec, awaiting);
|
|
@@ -226,17 +284,20 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
226
284
|
${inst.model ? html`<button type="button" class="hd-chip" title=${t("chat.chipModel", inst.model)} aria-label=${t("chat.chipModel", inst.model)} onClick=${() => pick("model")}>${inst.model}</button>` : null}
|
|
227
285
|
${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}`;
|
|
228
286
|
return html`<div class=${`panel p-chat${wrap ? " wrap-code" : ""}`} ref=${view}>
|
|
229
|
-
<${PanelHeader} title=${headerName(namesOf(inst || { name }, names.primary))} sub=${sub} nav=${html`<${InstanceSwitch} name=${name} current="chat" />`}><${HeaderTools} navKey=${navKey} /><${Menu} items=${items} label=${t("app.more")} /></${PanelHeader}>
|
|
287
|
+
<${PanelHeader} title=${headerName(namesOf(inst || { name }, names.primary))} sub=${sub} nav=${html`<${InstanceSwitch} name=${name} current="chat" />`}><${ChatEffectPosters} name=${name} lease=${lease} element=${effectSurface} /><${ChatEffects} name=${name} lease=${lease} element=${effectSurface} store=${store} open=${dialog === "effects"} onClose=${() => setDialog(null)} /><${HeaderTools} navKey=${navKey} /><${Menu} items=${items} label=${t("app.more")} /></${PanelHeader}>
|
|
288
|
+
<${ChatCard} key=${name} name=${name} card=${card} />
|
|
289
|
+
<${AgentWidget} slot="chat_header" widget=${widgetFor(name, "chat_header")} binds=${instanceBinds(inst, exec, awaiting)} />
|
|
230
290
|
<div class="chat-split" ref=${split}>
|
|
231
|
-
<div class
|
|
291
|
+
<div ref=${effectSurface} class=${`chat-main${theme && theme.background ? " has-th-bg" : ""}`}>
|
|
292
|
+
${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}
|
|
232
293
|
<${Thread} name=${name} th=${thread} />
|
|
233
|
-
<${Dock} name=${name} inst=${inst} lease=${lease} exec=${exec} awaiting=${awaiting} />
|
|
294
|
+
<${Dock} name=${name} inst=${inst} lease=${lease} exec=${exec} awaiting=${awaiting} workingLabel=${theme ? theme.workingLabel : undefined} />
|
|
234
295
|
</div>
|
|
235
296
|
<${PreviewPanel} name=${name} split=${split} msgs=${() => store.state.msgs[name] || []} subscribe=${(fn) => store.subscribe(fn)}
|
|
236
297
|
keyOf=${msgKey} reveal=${(k) => thread.current && thread.current.reveal(k)} download=${downloadHtml} allow=${(commit) => setPreviewOptIn(true, commit)} />
|
|
237
298
|
</div>
|
|
238
299
|
<${LightboxHost} />
|
|
239
|
-
<div class="drop-overlay" aria-hidden="true"><div class="drop-card"><${Icon} name="attach" size=${32} /><span>${t("chat.dropHere")}</span></div></div>
|
|
300
|
+
<div data-trust="drop" class="drop-overlay" aria-hidden="true"><div class="drop-card"><${Icon} name="attach" size=${32} /><span>${t("chat.dropHere")}</span></div></div>
|
|
240
301
|
${dialog === "delete" ? html`<${DeleteDialog} name=${name} onClose=${() => setDialog(null)} />` : null}
|
|
241
302
|
</div>`;
|
|
242
303
|
}
|
|
@@ -247,9 +308,12 @@ function Thread({ name, th }) {
|
|
|
247
308
|
const [jump, setJump] = useState({ show: false, unseen: 0 });
|
|
248
309
|
const [empty, setEmpty] = useState(false);
|
|
249
310
|
useLayoutEffect(() => {
|
|
311
|
+
let live = true;
|
|
250
312
|
const thread = createThread(list.current, scroller.current, {
|
|
251
313
|
t, tf: t, isUser: (x) => store.isUser(x, name), onJump: setJump, onEmpty: setEmpty,
|
|
252
314
|
setPreviewOptIn, copyText, download: downloadHtml, toggleWrap,
|
|
315
|
+
canTryEffect: x => getAppEffects()?.canTry(x) === true,
|
|
316
|
+
startEffectTrial: x => { const seq = navStore.get().seq; void getAppEffects()?.startTrial(x).then(ok => { if (!ok && live && navStore.get().seq === seq && document.hasFocus?.() === true && document.visibilityState === "visible") toast(t("chat.effectTrialUnavailable")); }); },
|
|
253
317
|
clickReplyButton: (id, index) => store.clickReplyButton(name, id, index), // #1266
|
|
254
318
|
replyButtonBusy: (id) => store.state.rbBusy.has(id),
|
|
255
319
|
pollMine: (id) => store.state.pollMine[id], // #1823: this device's own vote
|
|
@@ -273,13 +337,15 @@ function Thread({ name, th }) {
|
|
|
273
337
|
else if (kind === "sent") thread.jumpLatest();
|
|
274
338
|
});
|
|
275
339
|
const offPv = P() ? P().onChange(() => thread.refreshCards()) : () => {};
|
|
340
|
+
const offTrials = getAppEffects()?.subscribeTrial(() => thread.render(store.state.msgs[name] || [])) ?? (() => {});
|
|
276
341
|
const offPanel = panelStore.subscribe(() => thread.refreshCards()); // a card shows whether the panel has its block
|
|
277
342
|
const onScroll = () => { store.state.scrollMemo[name] = thread.onScroll(); };
|
|
278
343
|
const sc = scroller.current, ls = list.current;
|
|
279
344
|
sc.addEventListener("scroll", onScroll, { passive: true });
|
|
280
345
|
ls.addEventListener("click", thread.onClick);
|
|
281
346
|
return () => {
|
|
282
|
-
|
|
347
|
+
live = false;
|
|
348
|
+
off(); offPv(); offPanel(); offTrials(); offEmojis(); offMentions();
|
|
283
349
|
sc.removeEventListener("scroll", onScroll);
|
|
284
350
|
ls.removeEventListener("click", thread.onClick);
|
|
285
351
|
thread.dispose(); // the whole thread goes: every preview in it stops first
|
|
@@ -289,18 +355,18 @@ function Thread({ name, th }) {
|
|
|
289
355
|
return html`<div class="scroller" ref=${scroller}>
|
|
290
356
|
<div class="thread" ref=${list} role="log" aria-live="off" aria-label=${t("chat.conversationWith", name)}></div>
|
|
291
357
|
${empty ? html`<${Empty} icon="chat" title=${t("chat.noMessages")} />` : null}
|
|
292
|
-
${jump.show ? html`<button type="button" class="jump-latest" onClick=${() => th.current && th.current.jumpLatest()}>
|
|
358
|
+
${jump.show ? html`<button data-trust="thread-control" type="button" class="jump-latest" onClick=${() => th.current && th.current.jumpLatest()}>
|
|
293
359
|
<${Icon} name="down" size=${16} />${jump.unseen > 0 ? t("chat.newBelow", jump.unseen) : t("chat.jumpLatest")}</button>` : null}
|
|
294
360
|
</div>`;
|
|
295
361
|
}
|
|
296
362
|
|
|
297
363
|
/** Everything under the thread: prompts, the working line, a failed send, files waiting, and the composer. */
|
|
298
|
-
function Dock({ name, inst, lease, exec, awaiting }) {
|
|
364
|
+
function Dock({ name, inst, lease, exec, awaiting, workingLabel }) {
|
|
299
365
|
useChatTick(name, ["state"]);
|
|
300
366
|
const s = store.state;
|
|
301
|
-
return html`<div class="dock"><div class="dock-col">
|
|
367
|
+
return html`<div data-trust="composer" class="dock"><div class="dock-col">
|
|
302
368
|
<${Prompts} name=${name} />
|
|
303
|
-
<${WorkBar} name=${name} lease=${lease} exec=${exec} awaiting=${awaiting} />
|
|
369
|
+
<${WorkBar} name=${name} lease=${lease} exec=${exec} awaiting=${awaiting} workingLabel=${workingLabel} />
|
|
304
370
|
<${CommandCard} name=${name} lease=${lease} />
|
|
305
371
|
<${QuickActions} name=${name} inst=${inst} lease=${lease} />
|
|
306
372
|
${s.failedSends[name] ? html`<div class="failed-send" role="alert"><span>${t("chat.notSent")}</span><span class="txt">${s.failedSends[name]}</span>
|
|
@@ -347,14 +413,14 @@ function QuickActions({ name, inst, lease }) {
|
|
|
347
413
|
function Prompts({ name }) {
|
|
348
414
|
const list = store.promptsFor(name);
|
|
349
415
|
if (!list.length) return null;
|
|
350
|
-
return html`<div class="prompts" aria-live="polite">${list.map(p => html`<div key=${p.nonce} class=${`prompt${p.resolved ? " done" : ""}`} role="group" aria-label=${p.text}>
|
|
351
|
-
<div class="txt">${p.resolved ? (p.outcome || t("chat.promptGone")) : p.text}</div>
|
|
416
|
+
return html`<div data-trust="permission" class="prompts" aria-live="polite">${list.map(p => html`<div key=${p.nonce} class=${`prompt${p.resolved ? " done" : ""}`} role="group" aria-label=${p.text}>
|
|
417
|
+
<div class="txt">${p.resolved ? (p.outcome || t("chat.promptGone")) : textLinkSegments(p.text, p.links).map(sg => sg.href ? html`<a data-trust="link" class="st-link" href=${sg.href}>${sg.text}</a>` : sg.text)}</div>
|
|
352
418
|
${p.resolved ? null : html`<div class="acts">${p.actions.map(a => html`<button key=${a.id} type="button" class="btn" disabled=${!!p.busy} onClick=${() => store.answerPrompt(p, a.id)}>${a.label}</button>`)}
|
|
353
419
|
${p.busy ? html`<span class="wait">${t("chat.promptAnswering")}</span>` : null}</div>`}</div>`)}</div>`;
|
|
354
420
|
}
|
|
355
421
|
|
|
356
422
|
/** "<name> is working… 0:42", "looks stuck", "Stopping…", or what it waits on at its terminal. Text only. */
|
|
357
|
-
function WorkBar({ name, lease, exec, awaiting }) {
|
|
423
|
+
function WorkBar({ name, lease, exec, awaiting, workingLabel }) {
|
|
358
424
|
const s = store.state;
|
|
359
425
|
const busy = R().isBusy(exec) ? exec : "";
|
|
360
426
|
const kind = awaiting != null ? "awaiting" : s.stopping[name] && busy ? "stopping" : busy;
|
|
@@ -365,7 +431,9 @@ function WorkBar({ name, lease, exec, awaiting }) {
|
|
|
365
431
|
return () => lease.clear(h);
|
|
366
432
|
}, [kind, lease]);
|
|
367
433
|
if (!kind) return null;
|
|
368
|
-
|
|
434
|
+
// #1629: the agent's own working label (its theme), shown as text — only for "working", never for the other states.
|
|
435
|
+
const label = kind === "working" && workingLabel ? workingLabel
|
|
436
|
+
: t(kind === "stuck" ? "chat.stuck" : kind === "stopping" ? "chat.stopping" : kind === "awaiting" ? "chat.awaiting" : "chat.working", name);
|
|
369
437
|
const since = s.workingSince[name];
|
|
370
438
|
return html`<div class=${`work-bar ${kind}`}><span class="pulse" aria-hidden="true"></span><span class="lbl">${label}</span>
|
|
371
439
|
${kind === "awaiting" ? html`<span class="note" title=${t("chat.approxNote")}>${awaiting || t("chat.approxNote")}</span>`
|
|
@@ -428,7 +496,7 @@ function Composer({ name, lease, busy }) {
|
|
|
428
496
|
};
|
|
429
497
|
const optId = (c) => `cmd-${c.name}`;
|
|
430
498
|
return html`<div class="composer-box">
|
|
431
|
-
${palette ? html`<ul class="cmd-palette" id="cmdPalette" role="listbox" aria-label=${t("chat.cmdCommands")}>
|
|
499
|
+
${palette ? html`<ul data-trust="palette" class="cmd-palette" id="cmdPalette" role="listbox" aria-label=${t("chat.cmdCommands")}>
|
|
432
500
|
${palette.length ? palette.map((c) => html`<li key=${c.name} id=${optId(c)} role="option" aria-selected=${c === pick ? "true" : "false"}
|
|
433
501
|
class=${c === pick ? "on" : ""} onMouseDown=${(e) => { e.preventDefault(); complete(c); }}>
|
|
434
502
|
<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>`)
|
package/dist/ui/panel-details.js
CHANGED
|
@@ -16,6 +16,10 @@ import { settingsPath } from "/assets/app-route.js";
|
|
|
16
16
|
import { InstanceSwitch } from "/assets/instance-switch.js";
|
|
17
17
|
import { HeaderTools } from "/assets/header-tools.js";
|
|
18
18
|
import { Menu } from "/assets/ui-menu.js";
|
|
19
|
+
import { themeMenuItems, themesStore } from "/assets/app-themes.js";
|
|
20
|
+
import { colorMenuItem } from "/assets/identity-color.js";
|
|
21
|
+
import { IdentityCard, useCard } from "/assets/identity-card.js";
|
|
22
|
+
import { AgentWidget, instanceBinds, widgetFor } from "/assets/agent-widget.js";
|
|
19
23
|
import { Empty, ErrorState, Skeleton } from "/assets/ui-states.js";
|
|
20
24
|
import { toast } from "/assets/ui-toast.js";
|
|
21
25
|
import "./chat-strings.js";
|
|
@@ -27,10 +31,10 @@ export function DetailsPanel({ route, navKey }) {
|
|
|
27
31
|
const name = route.instance;
|
|
28
32
|
const inst = app.instances.find((i) => i.name === name) || null;
|
|
29
33
|
useEffect(() => { setTitle(`${name} · ${t("app.details")}`); }, [name, navKey]);
|
|
30
|
-
if (!app.ready) return html`<div class="panel p-details"><${PanelHeader} title=${name} /><div class="panel-body"><${Skeleton} lines=${6} /></div></div>`;
|
|
34
|
+
if (!app.ready) return html`<div data-effect-panel="details" class="panel p-details"><${PanelHeader} title=${name} /><div data-trust="panel-content" class="panel-body"><${Skeleton} lines=${6} /></div></div>`;
|
|
31
35
|
if (!inst) {
|
|
32
|
-
return html`<div class="panel p-details"><${PanelHeader} title=${name} />
|
|
33
|
-
<div class="panel-body center"><${Empty} icon="alert" title=${t("chat.notFound", name)} hint=${t("chat.notFoundHint")}
|
|
36
|
+
return html`<div data-effect-panel="details" class="panel p-details"><${PanelHeader} title=${name} />
|
|
37
|
+
<div data-trust="panel-content" class="panel-body center"><${Empty} icon="alert" title=${t("chat.notFound", name)} hint=${t("chat.notFoundHint")}
|
|
34
38
|
action=${html`<button type="button" class="btn only-narrow" onClick=${openDrawer}>${t("chat.backToList")}</button>`} /></div></div>`;
|
|
35
39
|
}
|
|
36
40
|
return html`<${DetailsView} key=${name} name=${name} inst=${inst} navKey=${navKey} exec=${app.exec[name]}
|
|
@@ -38,7 +42,10 @@ export function DetailsPanel({ route, navKey }) {
|
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
function DetailsView({ name, inst, navKey, exec, awaiting }) {
|
|
45
|
+
useStore(themesStore); // #1629: Lock / Unlock follows the themes as read
|
|
41
46
|
const lease = useLease(`details:${name}`);
|
|
47
|
+
// #1629 r4 §1: the identity card at the top, read when the status frame's card_v moves.
|
|
48
|
+
const card = useCard(name, inst.card_v);
|
|
42
49
|
const [d, setD] = useState(null);
|
|
43
50
|
const [failed, setFailed] = useState(false);
|
|
44
51
|
const [busy, setBusy] = useState("");
|
|
@@ -92,6 +99,8 @@ function DetailsView({ name, inst, navKey, exec, awaiting }) {
|
|
|
92
99
|
{ key: "edit", label: t("chat.dEditInSettings"), icon: "settings", onSelect: editInSettings },
|
|
93
100
|
st === "running" ? { key: "pause", label: t("chat.dPause"), icon: "pause", disabled: !!busy, onSelect: () => pauseWake("pause") } : null,
|
|
94
101
|
st === "paused" ? { key: "wake", label: t("chat.dWake"), icon: "play", disabled: !!busy, onSelect: () => pauseWake("wake") } : null,
|
|
102
|
+
colorMenuItem(name, inst), // #1958
|
|
103
|
+
...themeMenuItems(name), // #1629
|
|
95
104
|
] : [
|
|
96
105
|
{ key: "edit", label: t("chat.dEditInSettings"), icon: "settings", onSelect: editInSettings },
|
|
97
106
|
st === "running" ? null : st === "paused" ? null : { key: "start", label: t("chat.start"), icon: "play", disabled: !!busy, onSelect: () => lifecycle("start") },
|
|
@@ -99,6 +108,9 @@ function DetailsView({ name, inst, navKey, exec, awaiting }) {
|
|
|
99
108
|
st === "running" ? { key: "stop", label: t("chat.stopInstance"), icon: "stop", disabled: !!busy, onSelect: () => lifecycle("stop") } : null,
|
|
100
109
|
st === "running" ? { key: "pause", label: t("chat.dPause"), icon: "pause", disabled: !!busy, onSelect: () => pauseWake("pause") } : null,
|
|
101
110
|
st === "paused" ? { key: "wake", label: t("chat.dWake"), icon: "play", disabled: !!busy, onSelect: () => pauseWake("wake") } : null,
|
|
111
|
+
colorMenuItem(name, inst), // #1958: its identity colour (the public link too)
|
|
112
|
+
// #1629: a person's Lock and Reset of this agent's own web theme (not over the public link).
|
|
113
|
+
...themeMenuItems(name),
|
|
102
114
|
];
|
|
103
115
|
|
|
104
116
|
const cls = statusClass(inst, exec, awaiting);
|
|
@@ -141,8 +153,9 @@ function DetailsView({ name, inst, navKey, exec, awaiting }) {
|
|
|
141
153
|
<span class="mono muted">${a.timestamp ? new Date(`${a.timestamp.replace(" ", "T")}Z`).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : ""}</span>
|
|
142
154
|
<span class="ev">${a.event}</span> ${a.summary || ""}</div>`) : html`<p class="note">${t("chat.dNoActivity")}</p>`}</section>`;
|
|
143
155
|
}
|
|
144
|
-
return html`<div class="panel p-details">
|
|
156
|
+
return html`<div data-effect-panel="details" class="panel p-details">
|
|
145
157
|
<${PanelHeader} title=${name} sub=${sub} nav=${html`<${InstanceSwitch} name=${name} current="details" />`}><${HeaderTools} navKey=${navKey} /><${Menu} items=${items} label=${t("app.more")} /></${PanelHeader}>
|
|
146
|
-
<div class="panel-body"><div class="col" ref=${body}>${busy ? html`<p class="note" role="status">${t("chat.dWorking")}</p>` : null}
|
|
158
|
+
<div data-trust="panel-content" class="panel-body"><div class="col" ref=${body}>${busy ? html`<p class="note" role="status">${t("chat.dWorking")}</p>` : null}<${IdentityCard} name=${name} card=${card} />
|
|
159
|
+
<${AgentWidget} slot="details" widget=${widgetFor(name, "details")} binds=${instanceBinds(inst, exec, awaiting)} />${content}</div></div>
|
|
147
160
|
</div>`;
|
|
148
161
|
}
|
package/dist/ui/panel-fleet.js
CHANGED
|
@@ -134,11 +134,11 @@ export function FleetPanel({ route, navKey }) {
|
|
|
134
134
|
// #1685: host memory pressure slowing or holding new agents — shown here, where macOS's routine "elevated" is not a chat notice.
|
|
135
135
|
const { hostMemory } = useStore(appStore);
|
|
136
136
|
const sub = hostMemory ? html`<span class=${`status host-mem ${hostMemory}`} role="status"><span class=${`dot ${hostMemory === "critical" ? "bad" : "warn"}`} aria-hidden="true"></span>${t(`fleet.hostMemory_${hostMemory}`)}</span>` : null;
|
|
137
|
-
return html`<div class="panel p-fleet">
|
|
137
|
+
return html`<div data-effect-panel="fleet" class="panel p-fleet">
|
|
138
138
|
<${PanelHeader} title=${t("fleet.title")} sub=${sub}><${DocsLink} /><${HeaderTools} navKey=${navKey} /></${PanelHeader}>
|
|
139
|
-
<nav class="seg" aria-label=${t("fleet.title")}>${FLEET_TABS.map(k => html`<a key=${k} href=${fleetPath(k)} class=${`seg-item${k === tab ? " active" : ""}`}
|
|
139
|
+
<nav data-trust="panel-navigation" class="seg" aria-label=${t("fleet.title")}>${FLEET_TABS.map(k => html`<a key=${k} href=${fleetPath(k)} class=${`seg-item${k === tab ? " active" : ""}`}
|
|
140
140
|
aria-current=${k === tab ? "page" : undefined}><${Icon} name=${ICONS[k]} size=${16} /><span>${t(`fleet.${k}`)}</span></a>`)}</nav>
|
|
141
|
-
<div class="panel-body"><div class=${`col${tab === "org" || tab === "cache" ? " col-wide" : ""}`}><${Body} lease=${lease} /></div></div>
|
|
141
|
+
<div data-trust="panel-content" class="panel-body"><div class=${`col${tab === "org" || tab === "cache" ? " col-wide" : ""}`}><${Body} lease=${lease} /></div></div>
|
|
142
142
|
</div>`;
|
|
143
143
|
}
|
|
144
144
|
|
package/dist/ui/panel-log.js
CHANGED
|
@@ -200,10 +200,10 @@ export function LogPanel({ route, navKey }) {
|
|
|
200
200
|
const name = route.instance;
|
|
201
201
|
const inst = app.instances.find((i) => i.name === name) || null;
|
|
202
202
|
useEffect(() => { setTitle(`${name} · ${t("app.log")}`); }, [name, navKey]);
|
|
203
|
-
if (!app.ready) return html`<div class="panel p-log"><${PanelHeader} title=${name} /><div class="panel-body"><${Skeleton} lines=${6} /></div></div>`;
|
|
203
|
+
if (!app.ready) return html`<div data-effect-panel="log" class="panel p-log"><${PanelHeader} title=${name} /><div data-trust="panel-content" class="panel-body"><${Skeleton} lines=${6} /></div></div>`;
|
|
204
204
|
if (!inst) {
|
|
205
|
-
return html`<div class="panel p-log"><${PanelHeader} title=${name} />
|
|
206
|
-
<div class="panel-body center"><${Empty} icon="alert" title=${t("chat.notFound", name)} hint=${t("chat.notFoundHint")}
|
|
205
|
+
return html`<div data-effect-panel="log" class="panel p-log"><${PanelHeader} title=${name} />
|
|
206
|
+
<div data-trust="panel-content" class="panel-body center"><${Empty} icon="alert" title=${t("chat.notFound", name)} hint=${t("chat.notFoundHint")}
|
|
207
207
|
action=${html`<button type="button" class="btn only-narrow" onClick=${openDrawer}>${t("chat.backToList")}</button>`} /></div></div>`;
|
|
208
208
|
}
|
|
209
209
|
return html`<${LogView} key=${name} name=${name} inst=${inst} navKey=${navKey} exec=${app.exec[name]}
|
|
@@ -372,13 +372,13 @@ function LogView({ name, inst, navKey, exec, awaiting }) {
|
|
|
372
372
|
${items.length ? html`<ol class="log-list" role="log" aria-label=${t("app.log")}>${items.map((x, i) => html`<${Row} key=${`${x.kind}:${x.id}`} x=${x} prev=${items[i - 1]} />`)}</ol>`
|
|
373
373
|
: html`<p class="note log-empty">${tl("empty")}</p>`}`;
|
|
374
374
|
}
|
|
375
|
-
const tools = state.phase === "on" ? html`<div class="log-bar" role="group" aria-label=${tl("filter")}>
|
|
375
|
+
const tools = state.phase === "on" ? html`<div data-trust="panel-controls" class="log-bar" role="group" aria-label=${tl("filter")}>
|
|
376
376
|
${["all", "tools", "errors"].map((k) => html`<button key=${k} type="button" class=${`chip-btn${filter === k ? " on" : ""}`} aria-pressed=${filter === k ? "true" : "false"} onClick=${() => setFilter(k)}>${tl(k)}</button>`)}
|
|
377
377
|
<span class="grow"></span><span class="log-live" title=${tl("live")}><span class="dot running" aria-hidden="true"></span>${tl("live")}</span></div>` : null;
|
|
378
|
-
return html`<div class="panel p-log">
|
|
378
|
+
return html`<div data-effect-panel="log" class="panel p-log">
|
|
379
379
|
<${PanelHeader} title=${name} sub=${sub} nav=${html`<${InstanceSwitch} name=${name} current="log" />`}><${HeaderTools} navKey=${navKey} /><${HeaderSlot} /></${PanelHeader}>
|
|
380
380
|
${tools}
|
|
381
|
-
<div class="panel-body log-body" ref=${scroller} onScroll=${onScroll}><div class="col log-col">${content}</div></div>
|
|
381
|
+
<div data-trust="panel-content" class="panel-body log-body" ref=${scroller} onScroll=${onScroll}><div class="col log-col">${content}</div></div>
|
|
382
382
|
</div>`;
|
|
383
383
|
}
|
|
384
384
|
|
package/dist/ui/panel-needs.js
CHANGED
|
@@ -123,9 +123,9 @@ export function NeedsPanel({ navKey }) {
|
|
|
123
123
|
useEffect(() => { if (owner) return owner.subscribe(() => tick((n) => n + 1)); return undefined; }, [owner, lease]);
|
|
124
124
|
const groups = groupNeeds(items);
|
|
125
125
|
const nowMs = Date.now();
|
|
126
|
-
return html`<div class="panel p-needs">
|
|
126
|
+
return html`<div data-effect-panel="needs" class="panel p-needs">
|
|
127
127
|
<${PanelHeader} title=${tn("title")} sub=${items.length ? t("app.needsCount", items.length) : null} />
|
|
128
|
-
<div class="panel-body"><div class="col">
|
|
128
|
+
<div data-trust="panel-content" class="panel-body"><div class="col">
|
|
129
129
|
${!items.length ? html`<${Empty} icon="check" title=${tn("empty")} hint=${tn(chatApp ? "emptyHint" : "emptyHintWeb")} />`
|
|
130
130
|
: groups.map((g) => html`<section key=${g.instance} class="n-group" aria-label=${g.instance}>
|
|
131
131
|
<h2 class="n-group-head"><a href=${chatPath(g.instance)} title=${g.instance}>${nameFor(g.instance)}</a></h2>
|
|
@@ -175,7 +175,7 @@ function NeedsItem({ item, nowMs, owner }) {
|
|
|
175
175
|
setClaim("acks", item.id, res && res.ok ? "done" : null);
|
|
176
176
|
toast((body && (body.message || body.error)) || t("app.failed"), !!(res && res.ok));
|
|
177
177
|
};
|
|
178
|
-
return html`<article class=${`n-item${BAD.has(item.reason) ? " bad" : ""}`}>
|
|
178
|
+
return html`<article data-trust="permission" class=${`n-item${BAD.has(item.reason) ? " bad" : ""}`}>
|
|
179
179
|
<span class="n-icon"><${Icon} name=${ICON[item.type] || "alert"} size=${18} /></span>
|
|
180
180
|
<div class="n-body">
|
|
181
181
|
<div class="n-title"><strong>${reasonText(item)}</strong><span class="n-age">${age(item.since, nowMs)}</span></div>
|
|
@@ -41,9 +41,10 @@ import { takeAgentRequest } from "./settings-request.js";
|
|
|
41
41
|
import { setPreviewOptIn } from "./panel-chat.js";
|
|
42
42
|
import { startTour } from "./chat-tour.js";
|
|
43
43
|
import "./chat-strings.js";
|
|
44
|
+
import { AppearanceSection } from "./settings-appearance.js";
|
|
44
45
|
|
|
45
46
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
46
|
-
const ICONS = { device: "user", agents: "bot", bots: "plug", classic: "room", general: "sliders", web: "globe", advanced: "code" };
|
|
47
|
+
const ICONS = { device: "user", appearance: "palette", agents: "bot", bots: "plug", classic: "room", general: "sliders", web: "globe", advanced: "code" };
|
|
47
48
|
const RELEASES = "https://github.com/songsid/AgEnD/releases";
|
|
48
49
|
|
|
49
50
|
/** GET under the panel's lease → { ok, status, body }. */
|
|
@@ -224,6 +225,8 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
224
225
|
}, [!!ctx, navKey]);
|
|
225
226
|
// #1604: This device needs no configuration: shown at once, whatever the configuration read does.
|
|
226
227
|
const sectionBody = section === "device" ? html`<${DeviceSection} />`
|
|
228
|
+
// #1629 r4 §3: Appearance is the person's own (no configuration read): shown at once, like Preferences.
|
|
229
|
+
: section === "appearance" ? html`<${AppearanceSection} />`
|
|
227
230
|
: !data ? html`<${Skeleton} lines=${6} />`
|
|
228
231
|
: data.error ? html`<${ErrorState} message=${tn("configLoadFailed")} onRetry=${reload} />`
|
|
229
232
|
: section === "agents" ? html`<${Agents} ctx=${ctx} search=${search} openDialog=${(d) => setDialog({ ...d, key: navKey })} />`
|
|
@@ -236,14 +239,14 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
236
239
|
const open = dialog && dialog.key === navKey ? dialog : null;
|
|
237
240
|
const close = () => setDialog(null);
|
|
238
241
|
const searchable = section === "agents" || section === "bots" || section === "classic";
|
|
239
|
-
return html`<div class="panel p-settings">
|
|
242
|
+
return html`<div data-effect-panel="settings" class="panel p-settings">
|
|
240
243
|
<${PanelHeader} title=${tn("title")} sub=${data && data.version ? html`<a class="pill ver" href=${data.version.includes("-") ? `${RELEASES}/latest` : `${RELEASES}/tag/v${data.version}`} target="_blank" rel="noopener">v${data.version}</a>` : null}>
|
|
241
244
|
<button type="button" class="btn btn-ghost btn-sm" disabled=${!ctx} onClick=${() => setDialog({ kind: "wizard", key: navKey })}><${Icon} name="wand" size=${16} /><span class="hide-narrow">${tn("wizardButton")}</span></button>
|
|
242
245
|
<button type="button" class="icon-btn" aria-label=${tn("helpButton")} title=${tn("helpButton")} onClick=${() => setDialog({ kind: "help", key: navKey })}><${Icon} name="info" /></button>
|
|
243
246
|
</${PanelHeader}>
|
|
244
|
-
<nav class="seg" aria-label=${tn("title")}>${SETTINGS_SECTIONS.map((k) => html`<a key=${k} href=${settingsPath(k)} class=${`seg-item${k === section ? " active" : ""}`}
|
|
247
|
+
<nav data-trust="panel-navigation" class="seg" aria-label=${tn("title")}>${SETTINGS_SECTIONS.map((k) => html`<a key=${k} href=${settingsPath(k)} class=${`seg-item${k === section ? " active" : ""}`}
|
|
245
248
|
aria-current=${k === section ? "page" : undefined}><${Icon} name=${ICONS[k]} size=${16} /><span>${tn(`sec_${k}`)}</span></a>`)}</nav>
|
|
246
|
-
<div class="panel-body"><div class="col">
|
|
249
|
+
<div data-trust="panel-content" class="panel-body"><div class="col">
|
|
247
250
|
${searchable ? html`<div class="s-toolbar"><label class="s-search"><${Icon} name="search" size=${14} /><span class="sr-only">${tn("searchAll")}</span>
|
|
248
251
|
<input type="search" value=${search} placeholder=${tn("searchAll")} onInput=${(e) => setSearch(e.target.value)} /></label></div>` : null}
|
|
249
252
|
${op ? html`<${OperationCard} op=${op} schema=${data && data.schema} />` : null}
|
|
@@ -915,7 +918,7 @@ function Developer({ ctx }) {
|
|
|
915
918
|
/** Staged, not applied: the count, what it will cost, Discard and Apply. */
|
|
916
919
|
function PendingBar({ staged, busy, onApply, onDiscard }) {
|
|
917
920
|
const kinds = [...new Set([...staged.values()].map((c) => c.impact))];
|
|
918
|
-
return html`<div class="s-pending" role="region" aria-label=${tn("pendingRegion")}>
|
|
921
|
+
return html`<div data-trust="panel-controls" class="s-pending" role="region" aria-label=${tn("pendingRegion")}>
|
|
919
922
|
<strong>${tn("changesPending", staged.size)}</strong>
|
|
920
923
|
<span class="note">${kinds.map(impactText).join(" · ")}</span>
|
|
921
924
|
<span class="grow"></span>
|
package/dist/ui/preview-panel.js
CHANGED
|
@@ -16,6 +16,7 @@ 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 { EffectTrialPanel } from "./effect-trial-panel.js";
|
|
19
20
|
import { chatTime, onDayChange } from "./chat-time.js";
|
|
20
21
|
|
|
21
22
|
const R = () => globalThis.AgendChatRender;
|
|
@@ -43,14 +44,25 @@ export const panelClaims = () => claims;
|
|
|
43
44
|
/** The frame's key in preview.js: its own, so the card that opened it keeps its own state. */
|
|
44
45
|
export const frameKey = (key) => `panel:${key}`;
|
|
45
46
|
/** Show `spec` in the panel; `run`: start it there (the card's "Open in panel" click). The block shown before stops. */
|
|
47
|
+
function retire(open) {
|
|
48
|
+
if (!open) return;
|
|
49
|
+
if (open.kind === "trial") { try { open.handle.stop(); } catch { /* host controller owns cleanup */ } }
|
|
50
|
+
else if (P()) P().stop(frameKey(open.key), "stopped", "");
|
|
51
|
+
}
|
|
46
52
|
export function openPanel(spec, { run = false } = {}) {
|
|
47
|
-
|
|
48
|
-
set({ open: { ...spec }, run: run ? ++runs : 0 });
|
|
53
|
+
const old = state.open;
|
|
54
|
+
set({ open: { ...spec, kind: "html" }, run: run ? ++runs : 0 });
|
|
55
|
+
retire(old); // State changes first: cleanup reentry cannot close a newer panel.
|
|
56
|
+
}
|
|
57
|
+
/** A checked controller handle enters the existing component slot; no HTML run token is issued. */
|
|
58
|
+
export function openTrialPanel(spec) {
|
|
59
|
+
const old = state.open;
|
|
60
|
+
set({ open: { ...spec, kind: "trial" }, run: 0 }); retire(old);
|
|
49
61
|
}
|
|
50
|
-
/** Close the
|
|
62
|
+
/** Close publishes the empty slot before old-owner callbacks can reenter. */
|
|
51
63
|
export function closePanel() {
|
|
52
|
-
|
|
53
|
-
|
|
64
|
+
const old = state.open; if (!old) return;
|
|
65
|
+
set({ open: null, run: 0 }); retire(old);
|
|
54
66
|
}
|
|
55
67
|
/** The card key the panel shows, or null. */
|
|
56
68
|
export const shownKey = () => (state.open ? state.open.key : null);
|
|
@@ -67,7 +79,7 @@ export function htmlTitle(code) {
|
|
|
67
79
|
* without one, the newest block does. `msgs` in order; `keyOf` makes a message's key.
|
|
68
80
|
*/
|
|
69
81
|
export function newerVersion(open, msgs, keyOf) {
|
|
70
|
-
if (!open || !Array.isArray(msgs)) return null;
|
|
82
|
+
if (!open || open.kind === "trial" || !Array.isArray(msgs)) return null;
|
|
71
83
|
const at = msgs.findIndex(x => keyOf(x) === open.msgKey);
|
|
72
84
|
if (at < 0) return null;
|
|
73
85
|
// An attached file: a later agent reply attaching a file of the same name is its newer version. Its HTML is read
|
|
@@ -132,7 +144,8 @@ export function PreviewPanel({ name, split, msgs, subscribe, keyOf, reveal, down
|
|
|
132
144
|
const pending = useRef(0); // #1704: the card's click, waiting for this device's Allow (the st.run it came with)
|
|
133
145
|
const ownAllow = useRef(0); // while this panel's own Allow commits its choice: the click it answers (not cancelled by it)
|
|
134
146
|
const holder = useRef(null), divider = useRef(null), panel = useRef(null);
|
|
135
|
-
const
|
|
147
|
+
const trial = st.open?.kind === "trial" && st.open.instance === name ? st.open : null;
|
|
148
|
+
const open = st.open?.kind !== "trial" && st.open?.instance === name ? st.open : null;
|
|
136
149
|
|
|
137
150
|
useEffect(() => panelStore.subscribe(setSt), []);
|
|
138
151
|
// This device's choice (here or in another tab) decides whether Preview is offered: re-read it when it changes.
|
|
@@ -231,6 +244,9 @@ export function PreviewPanel({ name, split, msgs, subscribe, keyOf, reveal, down
|
|
|
231
244
|
// Esc in the panel closes it (and is not the chat's "stop the reply").
|
|
232
245
|
const onPanelKey = (e) => { if (e.key === "Escape" && !e.isComposing) { e.preventDefault(); closePanel(); } };
|
|
233
246
|
|
|
247
|
+
if (trial) return html`<${EffectTrialPanel} binding=${trial.binding} title=${trial.handle.reply.offer.title}
|
|
248
|
+
current=${trial.handle.current} ownerCss=${trial.ownerCss} onStage=${trial.onStage}
|
|
249
|
+
onStop=${trial.handle.stop} onClose=${() => { if (panelStore.get().open === trial) closePanel(); }} />`;
|
|
234
250
|
if (!open) return null;
|
|
235
251
|
const going = run.state === "starting" || run.state === "running";
|
|
236
252
|
const a = P().availability();
|
|
@@ -250,9 +266,9 @@ export function PreviewPanel({ name, split, msgs, subscribe, keyOf, reveal, down
|
|
|
250
266
|
if (want && pending.current === want && panelStore.get().run === want) start();
|
|
251
267
|
};
|
|
252
268
|
const source = t("chat.pvSource", open.sender || name, timeOf(open.ts));
|
|
253
|
-
return html`<div class="pv-divider" ref=${divider} role="separator" aria-orientation="vertical" tabindex="0" aria-label=${t("chat.pvResize")}
|
|
269
|
+
return html`<div data-trust="preview" class="pv-divider" ref=${divider} role="separator" aria-orientation="vertical" tabindex="0" aria-label=${t("chat.pvResize")}
|
|
254
270
|
aria-valuemin=${String(MIN_PANEL)} onKeyDown=${onKey} onPointerDown=${onDown}></div>
|
|
255
|
-
<aside class="pv-panel" ref=${panel} aria-label=${t("chat.pvPanel")} onKeyDown=${onPanelKey}>
|
|
271
|
+
<aside data-trust="preview" class="pv-panel" ref=${panel} aria-label=${t("chat.pvPanel")} onKeyDown=${onPanelKey}>
|
|
256
272
|
<div class="pv-panel-head">
|
|
257
273
|
<button type="button" class="icon-btn only-narrow pv-panel-close" title=${t("chat.pvBack")} aria-label=${t("chat.pvBack")} onClick=${closePanel}><${Icon} name="back" size=${18} /></button>
|
|
258
274
|
<div class="pv-panel-title"><strong>${t("chat.pvPanel")}</strong>
|