@songsid/agend 2.3.0-beta.6 → 2.3.0-beta.7
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 +1 -1
- package/dist/agent-cli.js +21 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +4 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.js +68 -8
- package/dist/agent-file-read.js.map +1 -1
- package/dist/agent-whats-new.js +4 -0
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.js +3 -1
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.d.ts +38 -0
- package/dist/authority/authority-state.js +114 -0
- package/dist/authority/authority-state.js.map +1 -0
- package/dist/authority/session-authorizations.d.ts +43 -0
- package/dist/authority/session-authorizations.js +102 -0
- package/dist/authority/session-authorizations.js.map +1 -0
- package/dist/authority/web-authority.d.ts +31 -11
- package/dist/authority/web-authority.js +39 -12
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/cache-ledger.d.ts +16 -3
- package/dist/cache-ledger.js +33 -13
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-service.d.ts +55 -3
- package/dist/cache-service.js +152 -16
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/mcp-tools.js +12 -6
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/classic-channel-manager.d.ts +2 -2
- package/dist/classic-channel-manager.js +2 -6
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/config-validator.js +10 -4
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.js +2 -0
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +8 -0
- package/dist/delivery-outbox.js +7 -1
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/fleet-manager.d.ts +109 -2
- package/dist/fleet-manager.js +280 -12
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +2 -1
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +2 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +81 -25
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +55 -8
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
- package/dist/human-delivery-receipts.d.ts +14 -0
- package/dist/human-delivery-receipts.js +42 -6
- package/dist/human-delivery-receipts.js.map +1 -1
- package/dist/instance-names.d.ts +4 -0
- package/dist/instance-names.js +12 -0
- package/dist/instance-names.js.map +1 -0
- package/dist/instructions.d.ts +8 -1
- package/dist/instructions.js +9 -2
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +52 -0
- package/dist/locale.js.map +1 -1
- package/dist/outbound-handlers.d.ts +6 -0
- package/dist/outbound-handlers.js +45 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +57 -1
- package/dist/outbound-schemas.js +33 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +8 -2
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/rollout-index.d.ts +5 -0
- package/dist/rollout-index.js +26 -5
- package/dist/rollout-index.js.map +1 -1
- package/dist/settings-api.d.ts +6 -0
- package/dist/settings-api.js +50 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +27 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +8 -1
- package/dist/settings-effect.js.map +1 -1
- package/dist/tool-permissions.d.ts +0 -7
- package/dist/tool-permissions.js +73 -5
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +2 -1
- package/dist/topic-commands.js +2 -4
- package/dist/topic-commands.js.map +1 -1
- package/dist/types.d.ts +13 -2
- package/dist/ui/app-effects.js +43 -25
- package/dist/ui/chat-ambient.js +306 -0
- package/dist/ui/chat-effect-posters.js +6 -7
- package/dist/ui/chat-effects.js +38 -15
- package/dist/ui/chat-strings.js +17 -4
- package/dist/ui/panel-chat.js +15 -7
- package/dist/ui/panel-settings.js +4 -1
- package/dist/ui/settings-appearance.js +8 -0
- package/dist/ui/settings-mods.js +19 -6
- package/dist/ui/settings-owner-effects.js +3 -3
- package/dist/ui/settings-strings.js +28 -0
- package/dist/ui/settings-web-authority.js +91 -0
- package/dist/ui/shared/app-session.js +2 -0
- package/dist/ui/shared/app-shell.js +2 -1
- package/dist/ui/shared/app-themes.js +24 -6
- package/dist/ui/shared/app.css +28 -0
- package/dist/ui/shared/effect-app-runtime.js +67 -18
- package/dist/ui/shared/effect-chat-runtime.js +16 -3
- package/dist/ui/shared/effect-frame.js +17 -3
- package/dist/ui/shared/effect-policy.js +18 -4
- package/dist/ui/shared/effect-registry.js +11 -18
- package/dist/ui/shared/effect-stock-trust.js +2 -2
- package/dist/ui/shared/effect-trial-stage.js +18 -7
- package/dist/ui/shared/fx-motion.js +58 -0
- package/dist/ui/shared/web-authorize.js +151 -0
- package/dist/usage-report.d.ts +147 -0
- package/dist/usage-report.js +195 -0
- package/dist/usage-report.js.map +1 -0
- package/dist/usage-rollup.d.ts +51 -0
- package/dist/usage-rollup.js +168 -0
- package/dist/usage-rollup.js.map +1 -0
- package/dist/web-ambient.d.ts +72 -0
- package/dist/web-ambient.js +154 -0
- package/dist/web-ambient.js.map +1 -0
- package/dist/web-api.d.ts +32 -0
- package/dist/web-api.js +143 -18
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +3 -0
- package/dist/web-appearance-tools.js +18 -3
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.d.ts +14 -2
- package/dist/web-appearance-view.js +32 -5
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +15 -1
- package/dist/web-appearance.js +77 -1
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-asset-parts.d.ts +2 -2
- package/dist/web-asset-parts.js +3 -3
- package/dist/web-asset-parts.js.map +1 -1
- package/dist/web-auth.js +3 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-effect-bootstrap.d.ts +12 -0
- package/dist/web-effect-bootstrap.js +49 -0
- package/dist/web-effect-bootstrap.js.map +1 -0
- package/dist/web-effect-bundle.js +46 -1
- package/dist/web-effect-bundle.js.map +1 -1
- package/dist/web-effect-playback.d.ts +30 -0
- package/dist/web-effect-playback.js +71 -0
- package/dist/web-effect-playback.js.map +1 -0
- package/dist/web-effect-profiles.d.ts +4 -4
- package/dist/web-effect-service.d.ts +11 -4
- package/dist/web-effect-service.js +25 -4
- package/dist/web-effect-service.js.map +1 -1
- package/dist/web-effect-tools.d.ts +1 -0
- package/dist/web-effect-tools.js +2 -1
- package/dist/web-effect-tools.js.map +1 -1
- package/dist/web-effect-worker.d.ts +1 -1
- package/dist/web-effect-worker.js +2 -2
- package/dist/web-effect-worker.js.map +1 -1
- package/dist/web-guest-access.js +3 -1
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-mod-agent.js +5 -1
- package/dist/web-mod-agent.js.map +1 -1
- package/dist/web-mod-bundle.d.ts +9 -1
- package/dist/web-mod-bundle.js +62 -3
- package/dist/web-mod-bundle.js.map +1 -1
- package/dist/web-mod-export.d.ts +9 -0
- package/dist/web-mod-export.js +21 -0
- package/dist/web-mod-export.js.map +1 -1
- package/dist/web-mod-store.d.ts +2 -1
- package/dist/web-mod-store.js +13 -6
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-session-authorization.d.ts +132 -0
- package/dist/web-session-authorization.js +193 -0
- package/dist/web-session-authorization.js.map +1 -0
- package/dist/web-session.d.ts +5 -0
- package/dist/web-session.js +16 -0
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +24 -2
- package/dist/web-settings-patch.js +47 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +8 -0
- package/dist/web-settings-tools.js +14 -1
- package/dist/web-settings-tools.js.map +1 -1
- package/dist/web-theme-store.d.ts +39 -0
- package/dist/web-theme-store.js +122 -11
- package/dist/web-theme-store.js.map +1 -1
- package/package.json +1 -1
package/dist/ui/chat-effects.js
CHANGED
|
@@ -7,17 +7,20 @@ import { chatEffectState, chatEffectIdentity } from "/assets/effect-chat-runtime
|
|
|
7
7
|
import { Dialog } from "/assets/ui-dialog.js";
|
|
8
8
|
import { navigate } from "/assets/app-nav.js";
|
|
9
9
|
import { toast } from "/assets/ui-toast.js";
|
|
10
|
+
import { setThemesShown, themesShown, themesStore } from "/assets/app-themes.js";
|
|
10
11
|
import { getAppEffects } from "./app-effects.js";
|
|
11
12
|
|
|
12
13
|
register("chatFx", {
|
|
13
|
-
en: {
|
|
14
|
+
en: { transport: "Off: the isolated effect frame could not start. Reload to try again.", enableHere: "Turn effects on here", viewerShow: "Show themes, effects and widgets here", clock: "Off: the page timing clock is unavailable. Reload to try again.", gatesUnknown: "Off: the playback conditions could not be verified.", resource: "Off: this effect could not be loaded or verified. Reload to try again.", frontQueue: "Waiting for a foreground slot.", consent: "This page has older playback information. Reload to use the isolated effect player.", previewMissing: "Off: the isolated effect frame is unavailable. Reload to try again.", unsupported: "Off: this browser lacks required Canvas Worker or layout support.", calibration: "Measuring this page's frame rate before playback.", baseline: "Off: this page's frame-rate calibration failed. Reload to try again.", safetyUnknown: "Stopped: this device's safety history could not be verified. Reload to check it.", rawReason: "Playback blocked: {0}", missingRow: "Playback state is unavailable (runtime_unavailable).", region: "Off: this effect does not declare the chat region.", blocked: "Placed effects are blocked for this viewer:", deviceSetting: "Open device preferences", webSetting: "Open Web settings", reloadHint: "Reload page", consentHint: "Allow HTML previews in device preferences.",
|
|
15
|
+
title: "Effects", loading: "Checking effects…", empty: "No effects in this chat.", failed: "Effects stopped because their resources could not be verified. Reload the page to try again.", reload: "Reload page", pause: "Pause", resume: "Resume", remove: "Remove from chat", removeFailed: "Could not remove this effect. Reload the page to check its state.", css: "Agent foreground effects are paused while your custom CSS is on or cannot be verified.", setting: "Open Web appearance settings", running: "Running", starting: "Starting", off: "Stopped", resumeFailed: "This effect cannot resume: the page or its safety history is not ready. Reload the page or choose another effect.", queued: "Waiting for a slot", unknown: "Playback is off or unavailable here.", budget: "Paused to protect page responsiveness. Resume keeps the three-pauses safety limit.", paused: "Paused", low: "Reduced quality",
|
|
14
16
|
typing: "Waiting while you type — it plays when you leave the message box.", covered: "Waiting while a menu or panel covers the chat.",
|
|
15
17
|
dialogOpen: "Paused while this dialog is open.", wasRunning: "Was running; paused while this dialog is open.", modal: "Waiting while a dialog is open.",
|
|
16
18
|
off3: "Turned off on this device after three automatic pauses. Resume cannot restart it here.", interrupted: "Stopped: the page closed while it was playing. Resume to play it again.",
|
|
17
19
|
cssOff: "Off while your custom CSS is on.", reducedMotion: "Off: this device asks for reduced motion.", saveData: "Off: data saver is on.",
|
|
18
20
|
previewOff: "Off: previews are not allowed on this device.", hiddenTab: "Waiting until this tab is visible.", offscreen: "Waiting until the chat is on screen.",
|
|
19
21
|
safeMode: "Off in safe mode.", publicLink: "Off on a public link.", disabled: "Effects are switched off." },
|
|
20
|
-
"zh-TW": {
|
|
22
|
+
"zh-TW": { transport: "已關閉:無法啟動隔離效果框架。請重新載入再試。", enableHere: "在這裡開啟效果", viewerShow: "在這裡顯示主題、效果與小工具", clock: "已關閉:頁面計時無法使用。請重新載入再試。", gatesUnknown: "已關閉:無法確認播放條件。", resource: "已關閉:無法載入或驗證此效果。請重新載入再試。", frontQueue: "等待前景空位。", consent: "這個頁面的播放資訊已過期。請重新載入以使用隔離效果播放器。", previewMissing: "已關閉:隔離效果框架無法使用。請重新載入再試。", unsupported: "已關閉:這個瀏覽器缺少 Canvas Worker 或版面驗證支援。", calibration: "正在量測頁面影格率,完成後才開始播放。", baseline: "已關閉:頁面影格率校準失敗。請重新載入再試。", safetyUnknown: "已停止:無法確認這台裝置的安全紀錄。請重新載入確認。", rawReason: "播放受阻:{0}", missingRow: "無法取得播放狀態(runtime_unavailable)。", region: "已關閉:此效果未宣告聊天區域。", blocked: "放置的效果目前無法在這個畫面播放:", deviceSetting: "開啟裝置偏好設定", webSetting: "開啟網頁設定", reloadHint: "重新載入頁面", consentHint: "請在裝置偏好設定允許 HTML 預覽。",
|
|
23
|
+
title: "效果", loading: "正在確認效果…", empty: "這個聊天沒有放置效果。", failed: "無法確認效果資源,已停止播放。重新載入頁面後再試。", reload: "重新載入頁面", pause: "暫停", resume: "恢復", remove: "從聊天移除", removeFailed: "無法移除效果。請重新載入頁面確認狀態。", css: "自訂 CSS 開啟或無法確認時,代理的前景效果會暫停。", setting: "開啟網頁外觀設定", running: "播放中", starting: "啟動中", off: "已停止", resumeFailed: "頁面或安全紀錄尚未就緒,無法恢復此效果。請重新載入頁面,或選擇其他效果。", queued: "等待空位", unknown: "這裡已關閉播放,或無法播放效果。", budget: "為維持頁面順暢,已暫停效果。恢復仍保留三次暫停的安全上限。", paused: "已暫停", low: "降低畫質",
|
|
21
24
|
typing: "輸入時暫停,離開訊息輸入框就會播放。", covered: "選單或面板蓋住聊天時暫停。",
|
|
22
25
|
dialogOpen: "開著這個對話框時暫停。", wasRunning: "原本在播放;開著這個對話框時暫停。", modal: "有對話框開著時暫停。",
|
|
23
26
|
off3: "已自動暫停三次,這台裝置已關閉此效果,無法在這裡恢復。", interrupted: "播放時頁面被關閉而停止。按「恢復」再播放一次。",
|
|
@@ -26,36 +29,53 @@ register("chatFx", {
|
|
|
26
29
|
safeMode: "安全模式下關閉。", publicLink: "公開連結上關閉。", disabled: "效果已關閉。" },
|
|
27
30
|
});
|
|
28
31
|
|
|
29
|
-
/**
|
|
32
|
+
/** Read/admin dashboard sessions keep management controls; public/guest Chat playback has no owner identity. */
|
|
30
33
|
export function chatEffectsMenuAvailable() { return chatEffectIdentity(window) !== null; }
|
|
31
34
|
|
|
32
35
|
// #2055: what a row's state means, in words — never only "Stopped" when the runtime knows why.
|
|
33
36
|
const RECENT_MS = 5000;
|
|
34
|
-
const
|
|
35
|
-
|
|
37
|
+
const REASON_KEY = { __proto__: null, transport_unavailable: "chatFx.transport", owner_css: "chatFx.cssOff", reduced_motion: "chatFx.reducedMotion", save_data: "chatFx.saveData", preview_unavailable: "chatFx.previewMissing", preview_not_consented: "chatFx.consent", unsupported_host: "chatFx.unsupported", registry_unsupported: "chatFx.unsupported",
|
|
38
|
+
baseline_unsupported: "chatFx.baseline", baseline_unavailable: "chatFx.baseline", baseline_timeout: "chatFx.baseline", calibrating: "chatFx.calibration", safety_unknown: "chatFx.safetyUnknown", region_unavailable: "chatFx.region",
|
|
36
39
|
hidden: "chatFx.hiddenTab", offscreen: "chatFx.offscreen", safe_mode: "chatFx.safeMode", public_link: "chatFx.publicLink", disabled: "chatFx.disabled",
|
|
40
|
+
clock_unavailable: "chatFx.clock", gates_unknown: "chatFx.gatesUnknown", runtime_unavailable: "chatFx.missingRow", resource_unavailable: "chatFx.resource", resource_timeout: "chatFx.resource", front_limit: "chatFx.frontQueue",
|
|
37
41
|
safety_off: "chatFx.off3", interrupted: "chatFx.interrupted" };
|
|
38
42
|
/** `ownOpen`: this Effects dialog is open; `wasRunning`: the effect was running when it opened (shown as past, never as current). */
|
|
39
43
|
export function effectRowText(row, { ownOpen = false, wasRunning = false, doc = typeof document === "undefined" ? null : document } = {}) {
|
|
40
|
-
if (!row) return t("chatFx.
|
|
44
|
+
if (!row) return t("chatFx.missingRow");
|
|
41
45
|
// The sticky history first (it is what Resume answers), then a pause, then what is playing or queued.
|
|
42
46
|
if (row.reason === "safety_off" || row.reason === "interrupted") return t(REASON_KEY[row.reason]);
|
|
43
47
|
if (row.reason === "paused" || row.reason === "host_budget") return t("chatFx.paused");
|
|
48
|
+
if (REASON_KEY[row.reason]) return t(REASON_KEY[row.reason]);
|
|
49
|
+
if (row.reason != null && !["stock_overlay", "modal", "frame_limit"].includes(row.reason))
|
|
50
|
+
return t("chatFx.rawReason", typeof row.reason === "string" ? row.reason.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 80) : "unknown");
|
|
44
51
|
if (row.phase === "running") return t("chatFx.running");
|
|
45
52
|
if (row.phase) return t("chatFx.starting");
|
|
46
53
|
if (row.verdict === "queued") return t("chatFx.queued");
|
|
47
54
|
if (row.reason === "stock_overlay" || row.reason === "modal") {
|
|
48
55
|
if (ownOpen) return t(wasRunning ? "chatFx.wasRunning" : "chatFx.dialogOpen");
|
|
49
56
|
if (row.reason === "modal") return t("chatFx.modal");
|
|
50
|
-
|
|
51
|
-
const a = doc?.activeElement;
|
|
52
|
-
return t(a && a !== doc.body && typeof a.matches === "function" && (a.matches(EDITABLE) || a.isContentEditable === true) ? "chatFx.typing" : "chatFx.covered");
|
|
57
|
+
return t("chatFx.covered");
|
|
53
58
|
}
|
|
54
|
-
return
|
|
59
|
+
return t("chatFx.rawReason", typeof row.reason === "string" ? row.reason.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 80) : "unknown");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Fixed local actions: no server/Worker-provided destination or raw reason in a URL. */
|
|
63
|
+
export function effectReasonAction(reason) {
|
|
64
|
+
if (reason === "preview_not_consented" || reason === "preview_unavailable") return { reload: true, label: "chatFx.reloadHint" };
|
|
65
|
+
if (["disabled", "owner_css"].includes(reason)) return { href: "/settings/appearance", label: "chatFx.setting" };
|
|
66
|
+
if (["transport_unavailable", "baseline_unsupported", "baseline_unavailable", "baseline_timeout", "runtime_unavailable", "resource_unavailable", "resource_timeout", "clock_unavailable", "safety_unknown"].includes(reason)) return { reload: true, label: "chatFx.reloadHint" };
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const localViewer = () => document.body.dataset.publicLink === "1" || document.body.dataset.tier === "guest";
|
|
70
|
+
function ReasonAction({ reason, onClose = () => {} }) {
|
|
71
|
+
if (reason === "disabled" && localViewer()) return html`<button type="button" class="btn" onClick=${() => { onClose(); setThemesShown(true); }}>${t("chatFx.enableHere")}</button>`;
|
|
72
|
+
const action = document.body.dataset.tier === "guest" && reason === "owner_css" ? { reload: true, label: "chatFx.reloadHint" } : effectReasonAction(reason);
|
|
73
|
+
return action ? html`<button type="button" class="btn" onClick=${() => { onClose(); if (action.reload) location.reload(); else navigate(action.href); }}>${t(action.label)}</button>` : null;
|
|
55
74
|
}
|
|
56
75
|
|
|
57
76
|
export function ChatEffects({ name, lease, element, store, open, onClose }) {
|
|
58
77
|
const cssLife = useStore(ownerCssLifecycle);
|
|
78
|
+
useStore(themesStore); // Viewer-local off/on is visible immediately, independent of runtime callbacks.
|
|
59
79
|
// Which effects were running just before this dialog opened ("was running" is said in the past, never shown as
|
|
60
80
|
// current). The ⋯ menu that opens it is a barrier too, so "just before" is within RECENT_MS of the observed stop.
|
|
61
81
|
// Tracked where updates arrive (an unchanged snapshot is not republished while an effect keeps running).
|
|
@@ -77,7 +97,7 @@ export function ChatEffects({ name, lease, element, store, open, onClose }) {
|
|
|
77
97
|
setBusy(false); onClose(); setCanWrite(false); setState(null);
|
|
78
98
|
const host = window, owner = getAppEffects(host);
|
|
79
99
|
const current = () => live && lease.current();
|
|
80
|
-
if (!owner) { setState({ error: null, rows: [], effects: [], unavailable: true }); return cleanup; }
|
|
100
|
+
if (!owner) { setState({ error: null, rows: [], effects: [], unavailable: true, reason: typeof host.OffscreenCanvas !== "function" || typeof host.Worker !== "function" ? "unsupported_host" : "runtime_unavailable" }); return cleanup; }
|
|
81
101
|
releases.push(owner.onEnd(() => { if (current()) setState({ error: "resource_unavailable", rows: [], effects: [], unavailable: true }); }));
|
|
82
102
|
playing.current = new Map(); // evidence belongs to this name and lease only
|
|
83
103
|
const track = rows => {
|
|
@@ -93,13 +113,13 @@ export function ChatEffects({ name, lease, element, store, open, onClose }) {
|
|
|
93
113
|
if (!current()) return;
|
|
94
114
|
track(value?.rows);
|
|
95
115
|
const g = owner.gates();
|
|
96
|
-
setState({ ...value, unavailable: !g.enabled || !g.show || !g.
|
|
116
|
+
setState({ ...value, unavailable: !g.enabled || !g.show || !g.transportReady || g.safe || g.reducedMotion || g.saveData });
|
|
97
117
|
};
|
|
98
118
|
api = owner.attachChat({ name, element: element.current, current,
|
|
99
119
|
observe: () => ({ state: chatEffectState(appStore.get(), name) }), onChange: changed });
|
|
100
120
|
runtime.current = api;
|
|
101
|
-
if (!api?.ok) { setState({ error: null, rows: [], effects: [], unavailable: true }); return cleanup; }
|
|
102
|
-
host.AgendAuth?.canWrite?.().then(value => { if (current()) setCanWrite(value === true); }).catch(() => {});
|
|
121
|
+
if (!api?.ok) { setState({ error: null, rows: [], effects: [], unavailable: true, reason: "runtime_unavailable" }); return cleanup; }
|
|
122
|
+
if (chatEffectIdentity(host)) host.AgendAuth?.canWrite?.().then(value => { if (current()) setCanWrite(value === true); }).catch(() => {});
|
|
103
123
|
api.sync();
|
|
104
124
|
return cleanup;
|
|
105
125
|
} catch {
|
|
@@ -117,11 +137,14 @@ export function ChatEffects({ name, lease, element, store, open, onClose }) {
|
|
|
117
137
|
wasOpen.current = open;
|
|
118
138
|
const css = cssLife.state !== "off" && effects.some(p => p.by !== "human" && p.manifest.layer === "front");
|
|
119
139
|
const remove = async id => { if (busy) return; setBusy(true); const api = runtime.current; const ok = await api?.remove?.(id); if (!lease.current() || runtime.current !== api) return; setBusy(false); if (!ok) toast(t("chatFx.removeFailed"), false); };
|
|
120
|
-
|
|
140
|
+
const blocked = effects.flatMap(p => { const row = rows.find(r => r.id === p.id); return row?.phase === "running" && row.reason == null ? [] : [{ p, row }]; });
|
|
141
|
+
return html`${!open && localViewer() && effects.length ? html`<div class="chat-fx-notice"><label><input type="checkbox" checked=${themesShown()} onChange=${e => setThemesShown(e.currentTarget.checked)} /> ${t("chatFx.viewerShow")}</label></div>` : null}${!open && state?.error && !effects.length ? html`<div class="chat-fx-notice" role="status">${effectRowText({ reason: state.error })} <${ReasonAction} reason=${state.error} /></div>` : null}${!open && state?.reason ? html`<div class="chat-fx-notice" role="status">${effectRowText({ reason: state.reason })} <${ReasonAction} reason=${state.reason} /></div>` : null}${!open && blocked.length ? html`<div class="chat-fx-notice" role="status"><strong>${t("chatFx.blocked")}</strong>
|
|
142
|
+
${blocked.map(({ p, row }) => html`<span key=${p.id}>${p.manifest.title}: ${effectRowText(row)} <${ReasonAction} reason=${row?.reason} /></span>`)}</div>` : null}${open ? html`<${Dialog} title=${t("chatFx.title")} onClose=${onClose} busy=${busy}>
|
|
121
143
|
${state?.error ? html`<p>${t("chatFx.failed")}</p><button type="button" class="btn" onClick=${() => location.reload()}>${t("chatFx.reload")}</button>` : null}
|
|
122
144
|
${css ? html`<p>${t("chatFx.css")}</p><button type="button" class="btn" onClick=${() => { onClose(); navigate("/settings/appearance"); }}>${t("chatFx.setting")}</button>` : null}
|
|
123
145
|
${!effects.length && !state?.error ? html`<p>${t(state?.unavailable ? "chatFx.unknown" : state?.loading || !state ? "chatFx.loading" : "chatFx.empty")}</p>` : null}
|
|
124
146
|
${effects.map(p => { const row = rows.find(r => r.id === p.id); return html`<section key=${p.id}><h3>${p.manifest.title}</h3><p>${effectRowText(row, { ownOpen: open, wasRunning: before.current?.get(p.id) === true })}${row?.quality === "low" ? ` · ${t("chatFx.low")}` : ""}</p>
|
|
147
|
+
<${ReasonAction} reason=${row?.reason} onClose=${onClose} />
|
|
125
148
|
${row?.reason === "host_budget" ? html`<p>${t("chatFx.budget")}</p>` : null}
|
|
126
149
|
<button type="button" class="btn" disabled=${busy} onClick=${() => runtime.current?.pause?.(p.id)}>${t("chatFx.pause")}</button>
|
|
127
150
|
<button type="button" class="btn" disabled=${busy} onClick=${() => { if (!runtime.current?.resume?.(p.id)) toast(t(rows.find(r => r.id === p.id)?.reason === "safety_off" ? "chatFx.off3" : "chatFx.resumeFailed"), false); }}>${t("chatFx.resume")}</button>
|
package/dist/ui/chat-strings.js
CHANGED
|
@@ -4,14 +4,14 @@ import { register } from "/assets/app-i18n.js";
|
|
|
4
4
|
register("chat", {
|
|
5
5
|
en: {
|
|
6
6
|
effectTrialReason_checking: "Checking this session…",
|
|
7
|
-
effectTrialReason_localAdmin: "Sign in as an admin
|
|
7
|
+
effectTrialReason_localAdmin: "Sign in as an admin outside the public link to Try.",
|
|
8
8
|
effectTrialReason_chatChanged: "Open the chat that offered this trial.",
|
|
9
9
|
effectTrialReason_busy: "Stop the current trial before starting another.",
|
|
10
10
|
effectTrialReason_safe: "Turn off safe mode to Try.",
|
|
11
11
|
effectTrialReason_effectsOff: "Turn on effects to Try.",
|
|
12
12
|
effectTrialReason_reducedMotion: "Your device requests reduced motion.",
|
|
13
13
|
effectTrialReason_saveData: "Your device requests reduced data use.",
|
|
14
|
-
effectTrialReason_transport: "The
|
|
14
|
+
effectTrialReason_transport: "The isolated effect player is unavailable. Reload this page.",
|
|
15
15
|
effectTrialReason_unsupported: "Canvas Worker playback is unavailable in this browser.",
|
|
16
16
|
effectTrialReason_inactive: "Return to this page to Try.",
|
|
17
17
|
effectTrialReason_dialog: "Close the other menu or dialog to Try.",
|
|
@@ -117,18 +117,25 @@ register("chat", {
|
|
|
117
117
|
themeResetDone: "{0}'s theme was reset.", themeLocked: "{0}'s theme is locked: the agent can't change it.", themeUnlocked: "{0}'s theme is unlocked.",
|
|
118
118
|
themeFailed: "The theme was not changed ({0}).",
|
|
119
119
|
widgetRemoveHeader: "Remove the chat header widget", widgetRemoveDetails: "Remove the Details widget", widgetRemoved: "{0}'s widget was removed.",
|
|
120
|
+
// #2117: an agent's ambient effect — why it is not showing here, and a person's Clear.
|
|
121
|
+
ambientBlocked_hidden: "Ambient effect hidden: agents' themes are off — turn them on",
|
|
122
|
+
ambientBlocked_safe: "Ambient effect hidden: safe mode (?safe=1) — leave safe mode",
|
|
123
|
+
ambientBlocked_reducedMotion: "Ambient effect hidden: this device follows reduced motion — play effects anyway",
|
|
124
|
+
ambientBlocked_unsupported: "Ambient effect hidden: this browser can't animate it",
|
|
125
|
+
ambientClearLoop: "Clear the ambient effect", ambientClearClick: "Clear the click effect", ambientCleared: "{0}'s ambient effect was cleared.",
|
|
126
|
+
ambientMotionNote: "Your system asks for reduced motion; effects are on.", ambientMotionNoteLink: "Turn off here", ambientMotionNoteClose: "Close",
|
|
120
127
|
configured: "(configured)", fleetDefault: "(fleet default)",
|
|
121
128
|
},
|
|
122
129
|
"zh-TW": {
|
|
123
130
|
effectTrialReason_checking: "正在確認工作階段…",
|
|
124
|
-
effectTrialReason_localAdmin: "
|
|
131
|
+
effectTrialReason_localAdmin: "請在公開連結以外的儀表板以管理員登入後試播。",
|
|
125
132
|
effectTrialReason_chatChanged: "請開啟發出試播邀請的聊天。",
|
|
126
133
|
effectTrialReason_busy: "請先停止目前的試播。",
|
|
127
134
|
effectTrialReason_safe: "請先關閉安全模式。",
|
|
128
135
|
effectTrialReason_effectsOff: "請先開啟效果。",
|
|
129
136
|
effectTrialReason_reducedMotion: "裝置目前要求減少動態效果。",
|
|
130
137
|
effectTrialReason_saveData: "裝置目前要求節省數據。",
|
|
131
|
-
effectTrialReason_transport: "
|
|
138
|
+
effectTrialReason_transport: "隔離效果播放器目前無法使用。請重新載入頁面。",
|
|
132
139
|
effectTrialReason_unsupported: "此瀏覽器無法播放 Canvas Worker 效果。",
|
|
133
140
|
effectTrialReason_inactive: "請回到本頁後試播。",
|
|
134
141
|
effectTrialReason_dialog: "請先關閉其他選單或對話框。",
|
|
@@ -231,6 +238,12 @@ register("chat", {
|
|
|
231
238
|
themeResetDone: "已重設 {0} 的主題。", themeLocked: "已鎖定 {0} 的主題:agent 無法變更。", themeUnlocked: "已解除 {0} 的主題鎖定。",
|
|
232
239
|
themeFailed: "主題沒有變更({0})。",
|
|
233
240
|
widgetRemoveHeader: "移除聊天標頭的小工具", widgetRemoveDetails: "移除詳細資料頁的小工具", widgetRemoved: "已移除 {0} 的小工具。",
|
|
241
|
+
ambientBlocked_hidden: "環境效果未顯示:已關閉 agent 的主題——開啟",
|
|
242
|
+
ambientBlocked_safe: "環境效果未顯示:安全模式(?safe=1)——離開安全模式",
|
|
243
|
+
ambientBlocked_reducedMotion: "環境效果未顯示:這台裝置跟隨「減少動態效果」——仍然播放",
|
|
244
|
+
ambientBlocked_unsupported: "環境效果未顯示:此瀏覽器無法播放",
|
|
245
|
+
ambientClearLoop: "清除環境效果", ambientClearClick: "清除點擊效果", ambientCleared: "已清除 {0} 的環境效果。",
|
|
246
|
+
ambientMotionNote: "你的系統要求減少動態效果;效果目前開著。", ambientMotionNoteLink: "在這裡關閉", ambientMotionNoteClose: "關閉",
|
|
234
247
|
configured: "(設定值)", fleetDefault: "(fleet 預設)",
|
|
235
248
|
},
|
|
236
249
|
});
|
package/dist/ui/panel-chat.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
|
|
10
10
|
import { t } from "/assets/app-i18n.js";
|
|
11
11
|
import { appStore, guestPage, useStore } from "/assets/app-store.js";
|
|
12
|
+
import { AuthorizeChip } from "/assets/web-authorize.js";
|
|
12
13
|
import { useLease } from "/assets/app-ctx.js";
|
|
13
14
|
import { modelText, namesOf, namesStore } from "/assets/instance-nav.js";
|
|
14
15
|
import { PanelHeader, onPanelKey, setTitle, statusClass, statusLabel, openDrawer, requestNewInstance } from "/assets/app-shell.js";
|
|
@@ -23,7 +24,7 @@ import { Icon } from "/assets/ui-icons.js";
|
|
|
23
24
|
import { toast } from "/assets/ui-toast.js";
|
|
24
25
|
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
25
26
|
import { savePref } from "/assets/app-prefs.js";
|
|
26
|
-
import { applyTheme, resolvedMode, themeFor, themeImageUrl, themeMenuItems, themesStore } from "/assets/app-themes.js";
|
|
27
|
+
import { applyTheme, clearAmbient, resolvedMode, themeFor, themeImageUrl, themeMenuItems, themesStore } from "/assets/app-themes.js";
|
|
27
28
|
import { colorMenuItem } from "/assets/identity-color.js";
|
|
28
29
|
import { IdentityCard, useCard } from "/assets/identity-card.js";
|
|
29
30
|
import { AgentWidget, instanceBinds, widgetFor } from "/assets/agent-widget.js";
|
|
@@ -56,6 +57,7 @@ import { installTour, refreshTourSpot } from "./chat-tour.js";
|
|
|
56
57
|
import { LightboxHost, openLightbox } from "./image-lightbox.js";
|
|
57
58
|
import { ChatEffectPosters } from "/ui/js/chat-effect-posters.js";
|
|
58
59
|
import { ChatEffects, chatEffectsMenuAvailable } from "./chat-effects.js";
|
|
60
|
+
import { AmbientMotionNote, ChatAmbient, ambientMenuItems } from "./chat-ambient.js";
|
|
59
61
|
import { startAppEffects, getAppEffects } from "./app-effects.js";
|
|
60
62
|
|
|
61
63
|
const R = () => globalThis.AgendChatRender;
|
|
@@ -116,8 +118,8 @@ export function boot({ stream, boot: bootData, deps = {} }) {
|
|
|
116
118
|
}
|
|
117
119
|
// #1604: the preview opt-in and Tour live in Settings → This device now (and on each preview card's menu).
|
|
118
120
|
installTour();
|
|
119
|
-
// #
|
|
120
|
-
|
|
121
|
+
// #2118: viewing current in-scope Chat effects needs no management or personal-slot authority.
|
|
122
|
+
startAppEffects({ store });
|
|
121
123
|
return store;
|
|
122
124
|
}
|
|
123
125
|
|
|
@@ -275,12 +277,14 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
275
277
|
colorMenuItem(name, inst),
|
|
276
278
|
// #1629: a person's control over this agent's theme — not over the public link (it only views themes).
|
|
277
279
|
...themeMenuItems(name),
|
|
280
|
+
// #2117: its ambient — why it is not showing here (with the way to change that), and a person's Clear.
|
|
281
|
+
...ambientMenuItems(name, { publicLink: document.body.dataset.publicLink === "1", clear: clearAmbient }),
|
|
278
282
|
chatEffectsMenuAvailable() ? { key: "effects", label: t("chatFx.title"), onSelect: () => setDialog("effects") } : null,
|
|
279
283
|
{ key: "delete", label: t("chat.delete"), icon: "trash", danger: true, onSelect: () => setDialog("delete") },
|
|
280
284
|
];
|
|
281
285
|
const cls = statusClass(inst, exec, awaiting);
|
|
282
286
|
// #1920 phase 3: a guest has this chat only — no Details, View or Log to switch to, no lifecycle, colour, theme,
|
|
283
|
-
//
|
|
287
|
+
// effect management or delete (a guest's status frame carries no model or effort either, so those chips do not show).
|
|
284
288
|
const guest = guestPage();
|
|
285
289
|
// #1269 quick actions: the model and effort it runs, each a way to /model or /effort (the same pick list).
|
|
286
290
|
const pick = (command) => store.runCommand(name, command, "", { alive: () => lease.current() });
|
|
@@ -288,14 +292,17 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
288
292
|
${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}
|
|
289
293
|
${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}`;
|
|
290
294
|
return html`<div class=${`panel p-chat${wrap ? " wrap-code" : ""}`} ref=${view}>
|
|
291
|
-
<${PanelHeader} title=${headerName(namesOf(inst || { name }, names.primary))} sub=${sub} nav=${guest ? null : html`<${InstanceSwitch} name=${name} current="chat" />`}
|
|
295
|
+
<${PanelHeader} title=${headerName(namesOf(inst || { name }, names.primary))} sub=${sub} nav=${guest ? null : html`<${InstanceSwitch} name=${name} current="chat" />`} notice=${html`<${ChatEffects} name=${name} lease=${lease} element=${effectSurface} store=${store} open=${dialog === "effects"} onClose=${() => setDialog(null)} />`}><${ChatEffectPosters} name=${name} lease=${lease} element=${effectSurface} /><${HeaderTools} navKey=${navKey} />${guest ? null : html`<${Menu} items=${items} label=${t("app.more")} />`}</${PanelHeader}>
|
|
292
296
|
<${ChatCard} key=${name} name=${name} card=${card} />
|
|
293
297
|
<${AgentWidget} slot="chat_header" widget=${widgetFor(name, "chat_header")} binds=${instanceBinds(inst, exec, awaiting)} />
|
|
294
298
|
<div class="chat-split" ref=${split}>
|
|
295
299
|
<div ref=${effectSurface} class=${`chat-main${theme && theme.background ? " has-th-bg" : ""}`}>
|
|
296
300
|
${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}
|
|
301
|
+
<${ChatAmbient} name=${name} surface=${effectSurface} part="layer" />
|
|
302
|
+
<${AmbientMotionNote} name=${name} />
|
|
297
303
|
<${Thread} name=${name} th=${thread} />
|
|
298
304
|
<${Dock} name=${name} inst=${inst} lease=${lease} exec=${exec} awaiting=${awaiting} workingLabel=${theme ? theme.workingLabel : undefined} />
|
|
305
|
+
<${ChatAmbient} name=${name} surface=${effectSurface} part="front" />
|
|
299
306
|
</div>
|
|
300
307
|
<${PreviewPanel} name=${name} split=${split} msgs=${() => store.state.msgs[name] || []} subscribe=${(fn) => store.subscribe(fn)}
|
|
301
308
|
keyOf=${msgKey} reveal=${(k) => thread.current && thread.current.reveal(k)} download=${downloadHtml} allow=${(commit) => setPreviewOptIn(true, commit)} />
|
|
@@ -319,8 +326,9 @@ function Thread({ name, th }) {
|
|
|
319
326
|
effectTrialStatus: x => getAppEffects()?.trialStatus(x) ?? "localAdmin",
|
|
320
327
|
fixEffectTrial: (x, reason) => {
|
|
321
328
|
if (reason === "busy") getAppEffects()?.stopTrial();
|
|
329
|
+
else if (reason === "transport") location.reload();
|
|
322
330
|
else if (reason === "localAdmin") location.href = "/signin?next=" + encodeURIComponent(location.pathname + location.search);
|
|
323
|
-
else navigate("/
|
|
331
|
+
else navigate("/settings/appearance");
|
|
324
332
|
},
|
|
325
333
|
canTryEffect: x => getAppEffects()?.canTry(x) === true,
|
|
326
334
|
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")); }); },
|
|
@@ -384,7 +392,7 @@ function Dock({ name, inst, lease, exec, awaiting, workingLabel }) {
|
|
|
384
392
|
<button type="button" class="btn btn-sm" onClick=${() => store.discardFailed(name)}>${t("chat.discard")}</button></div>` : null}
|
|
385
393
|
<${Composer} name=${name} lease=${lease} busy=${R().isBusy(exec)} />
|
|
386
394
|
<p class="hint" title=${t(guestPage() ? "chat.guestMore" : "chat.composerMore")}>${t("chat.composerHint")}</p>
|
|
387
|
-
${guestPage() ? html`<p class="guest-note">${t("chat.guestNote")}</p>` :
|
|
395
|
+
${guestPage() ? html`<p class="guest-note">${t("chat.guestNote")}</p>` : html`<${AuthorizeChip} />`}
|
|
388
396
|
</div></div>`;
|
|
389
397
|
}
|
|
390
398
|
|
|
@@ -44,6 +44,7 @@ import { startTour } from "./chat-tour.js";
|
|
|
44
44
|
import "./chat-strings.js";
|
|
45
45
|
import { AppearanceSection } from "./settings-appearance.js";
|
|
46
46
|
import { GuestsCard } from "./settings-guests.js";
|
|
47
|
+
import { WebAuthorityCard } from "./settings-web-authority.js";
|
|
47
48
|
|
|
48
49
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
49
50
|
const ICONS = { device: "user", appearance: "palette", agents: "bot", bots: "plug", classic: "room", general: "sliders", web: "globe", advanced: "code" };
|
|
@@ -772,6 +773,8 @@ export const WEB_READ_ONLY = [
|
|
|
772
773
|
function WebSection({ ctx, staged }) {
|
|
773
774
|
const { schema } = ctx;
|
|
774
775
|
const web = ctx.fleet.web || {}, local = (web.session && web.session.local) || {}, term = ctx.fleet.web_terminal || {};
|
|
776
|
+
// Over the public link the fields read only; the Guests and Web authority cards are not offered (the server never
|
|
777
|
+
// serves their reads there).
|
|
775
778
|
const overPublicLink = globalThis.document?.body?.dataset?.publicLink === "1";
|
|
776
779
|
const original = useMemo(() => ({
|
|
777
780
|
echo: web.echo_to_channel ?? true,
|
|
@@ -867,7 +870,7 @@ function WebSection({ ctx, staged }) {
|
|
|
867
870
|
</div>
|
|
868
871
|
</section>
|
|
869
872
|
</fieldset>
|
|
870
|
-
|
|
873
|
+
${overPublicLink ? null : html`<${GuestsCard} ctx=${ctx} staged=${staged} /><${WebAuthorityCard} ctx=${ctx} staged=${staged} />`}
|
|
871
874
|
<section class="card s-web-readonly"><h3>${tn("webReadOnlyTitle")}</h3>
|
|
872
875
|
<p class="note">${tn("webReadOnlyLead")}</p>
|
|
873
876
|
<dl class="s-web-ro">${WEB_READ_ONLY.map((k) => html`<div key=${k}><dt><code>${k}</code></dt><dd>${shown(k)}</dd></div>`)}</dl>
|
|
@@ -10,6 +10,7 @@ import { Icon } from "/assets/ui-icons.js";
|
|
|
10
10
|
import { toast } from "/assets/ui-toast.js";
|
|
11
11
|
import { ownerCssOffHere, ownerCssTrial, safeMode, setOwnerCssActive, setOwnerCssOffHere, startOwnerCssTrial } from "/assets/owner-css.js";
|
|
12
12
|
import { setThemesShown, themesStore } from "/assets/app-themes.js";
|
|
13
|
+
import { followsReducedMotion, onMotionChange, setFollowsReducedMotion } from "/assets/fx-motion.js";
|
|
13
14
|
import { CARD_SIZES, cardSizeDefault, cardSizeStore, setCardSizeDefault } from "/assets/identity-card.js";
|
|
14
15
|
import { AgentWidget, checkWidget, fleetBinds, setSidebarWidget, sidebarWidgetRaw, sidebarWidgetStore, widgetProblem } from "/assets/agent-widget.js";
|
|
15
16
|
import { confirmedWrite } from "./settings-confirm.js";
|
|
@@ -87,6 +88,7 @@ function IdentityColorCard() {
|
|
|
87
88
|
*/
|
|
88
89
|
export async function restoreAppearanceDefaults() {
|
|
89
90
|
setThemesShown(true);
|
|
91
|
+
setFollowsReducedMotion(false);
|
|
90
92
|
setCardSizeDefault("m");
|
|
91
93
|
savePref("card.sizeByInstance", null);
|
|
92
94
|
setSidebarWidget(null);
|
|
@@ -187,10 +189,16 @@ function CardLimitsCard() {
|
|
|
187
189
|
/** #1629 r2 (d): whether this person's screens show agents' themes (and, as they come, effects) — synced. */
|
|
188
190
|
function ThemesCard() {
|
|
189
191
|
const s = useStore(themesStore);
|
|
192
|
+
// #2117: this device's own switch (not synced): effects play unless it follows the system's reduced-motion setting.
|
|
193
|
+
const [follow, setFollow] = useState(followsReducedMotion);
|
|
194
|
+
useEffect(() => onMotionChange(() => setFollow(followsReducedMotion())), []);
|
|
190
195
|
return html`<section class="card s-appearance s-synced"><${CardTitle} title=${tn("themesTitle")} topic="themes" />
|
|
191
196
|
<label class="check"><input type="checkbox" checked=${s.shown} disabled=${safeMode()} onChange=${(e) => setThemesShown(e.target.checked)} />
|
|
192
197
|
<span>${tn("themesShow")}</span></label>
|
|
193
198
|
<p class="sub">${safeMode() ? tn("themesSafeMode") : tn("themesShowLead")}</p>
|
|
199
|
+
<label class="check"><input type="checkbox" id="fx-follow-reduced-motion" checked=${follow} onChange=${(e) => setFollowsReducedMotion(e.target.checked)} />
|
|
200
|
+
<span>${tn("themesFollowMotion")}</span></label>
|
|
201
|
+
<p class="sub">${tn("themesFollowMotionLead")}</p>
|
|
194
202
|
</section>`;
|
|
195
203
|
}
|
|
196
204
|
|
package/dist/ui/settings-mods.js
CHANGED
|
@@ -41,9 +41,9 @@ function why(body) {
|
|
|
41
41
|
|
|
42
42
|
/** The parts a plan offers, each with its default: everything but custom CSS. */
|
|
43
43
|
export function defaultChoice(plan) {
|
|
44
|
-
const c = { theme: false, identity: false, effects: false, ownerCss: false, widgets: {}, replace: false };
|
|
44
|
+
const c = { theme: false, identity: false, effects: false, ownerCss: false, ambient: false, widgets: {}, replace: false };
|
|
45
45
|
for (const it of plan.items) {
|
|
46
|
-
if (it.part === "theme" || it.part === "identity") c[it.part] = true;
|
|
46
|
+
if (it.part === "theme" || it.part === "identity" || it.part === "ambient") c[it.part] = true;
|
|
47
47
|
else if (it.part === "widget") c.widgets[it.slot] = true;
|
|
48
48
|
else if (it.part === "effect") c.effects = true;
|
|
49
49
|
// ownerCss stays false: "read it before turning it on" (r4 §4).
|
|
@@ -100,6 +100,19 @@ function PlanItem({ it, choice, setChoice, binds, fleet }) {
|
|
|
100
100
|
return html`<li class="s-mod-item">${tick(tn(`modPartWidget_${it.slot}`), !!choice.widgets[it.slot], (v) => setChoice({ ...choice, widgets: { ...choice.widgets, [it.slot]: v } }))}
|
|
101
101
|
<div class="s-mod-detail s-mod-widget">${w ? html`<${AgentWidget} slot=${it.slot} widget=${w} binds=${it.slot === "sidebar" ? fleet : binds} />` : null}</div></li>`;
|
|
102
102
|
}
|
|
103
|
+
if (it.part === "ambient") {
|
|
104
|
+
// #2117: each ambient as it will be drawn: its preset, glyphs (pictures shown) and colours; every field below.
|
|
105
|
+
const glyph = (g, i) => typeof g === "string" ? html`<span key=${i} class="s-mod-text">${g}</span>`
|
|
106
|
+
: html`<img key=${i} class="s-mod-glyph" src=${`/ui/mods/staged/${g.asset}`} alt="" loading="lazy" />`;
|
|
107
|
+
const one = (kind) => {
|
|
108
|
+
const a = it[kind];
|
|
109
|
+
if (!a || !Array.isArray(a.glyphs)) return null;
|
|
110
|
+
return html`<div key=${kind} class="s-mod-kv"><span class="k">${a.preset}</span><span class="v s-mod-sws">${a.glyphs.map(glyph)}${(a.colors || []).map((c) => html`<${Swatch} key=${c} color=${c} />`)}</span></div>`;
|
|
111
|
+
};
|
|
112
|
+
return html`<li class="s-mod-item">${tick(tn("modPartAmbient"), !!choice.ambient, (v) => setChoice({ ...choice, ambient: v }))}
|
|
113
|
+
<div class="s-mod-detail">${one("loop")}${one("click")}
|
|
114
|
+
<details class="s-mod-raw"><summary>${tn("modAllFields")}</summary><pre class="s-mod-css-text">${JSON.stringify({ loop: it.loop, click: it.click }, null, 2)}</pre></details></div></li>`;
|
|
115
|
+
}
|
|
103
116
|
if (it.part === "effect") {
|
|
104
117
|
return html`<li class="s-mod-item">${tick(tn("modPartEffect", it.title), choice.effects, (v) => setChoice({ ...choice, effects: v }))}
|
|
105
118
|
<div class="s-mod-detail"><span>${kib(it.bytes)} · ${tn("modRegions", it.regions.join(", "))}</span>${it.wants.length ? html`<span>${tn("modWants", it.wants.join(", "))}</span>` : null}</div></li>`;
|
|
@@ -120,7 +133,7 @@ function Plan({ staged, onDone }) {
|
|
|
120
133
|
const [target, setTarget] = useState("");
|
|
121
134
|
const [busy, setBusy] = useState(false);
|
|
122
135
|
const [error, setError] = useState("");
|
|
123
|
-
const toInstance = choice.theme || choice.identity || !!choice.widgets.chat_header || !!choice.widgets.details;
|
|
136
|
+
const toInstance = choice.theme || choice.identity || choice.ambient || !!choice.widgets.chat_header || !!choice.widgets.details;
|
|
124
137
|
const names = (app.instances || []).map((i) => i.name);
|
|
125
138
|
const install = async () => {
|
|
126
139
|
setBusy(true); setError("");
|
|
@@ -162,13 +175,13 @@ function Plan({ staged, onDone }) {
|
|
|
162
175
|
|
|
163
176
|
function ExportForm() {
|
|
164
177
|
const app = useStore(appStore);
|
|
165
|
-
const [f, setF] = useState({ instance: "", name: "", version: "1.0.0", title: "", theme: true, identity: true, header: true, details: true, sidebar: false, ownerCss: false });
|
|
178
|
+
const [f, setF] = useState({ instance: "", name: "", version: "1.0.0", title: "", theme: true, identity: true, header: true, details: true, ambient: true, sidebar: false, ownerCss: false });
|
|
166
179
|
const [error, setError] = useState("");
|
|
167
180
|
const set = (k) => (e) => setF({ ...f, [k]: e.target.type === "checkbox" ? e.target.checked : e.target.value });
|
|
168
181
|
const go = async () => {
|
|
169
182
|
setError("");
|
|
170
183
|
const body = { instance: f.instance || undefined, name: f.name, version: f.version, title: f.title,
|
|
171
|
-
include: { theme: f.theme, identity: f.identity, ownerCss: f.ownerCss, widgets: { chat_header: f.header, details: f.details, sidebar: f.sidebar } } };
|
|
184
|
+
include: { theme: f.theme, identity: f.identity, ambient: f.ambient, ownerCss: f.ownerCss, widgets: { chat_header: f.header, details: f.details, sidebar: f.sidebar } } };
|
|
172
185
|
let res;
|
|
173
186
|
try { res = await fetch("/ui/mods/export", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); }
|
|
174
187
|
catch { setError(tn("modFailed", "network")); return; }
|
|
@@ -182,7 +195,7 @@ function ExportForm() {
|
|
|
182
195
|
return html`<details class="s-mod-export"><summary>${tn("modExport")}</summary>
|
|
183
196
|
<p class="sub">${tn("modExportLead")}</p>
|
|
184
197
|
<label class="field"><span>${tn("modExportInstance")}</span><select value=${f.instance} onChange=${set("instance")}><option value="">—</option>${(app.instances || []).map((i) => html`<option key=${i.name} value=${i.name}>${i.name}</option>`)}</select></label>
|
|
185
|
-
<div class="s-mod-boxes">${box("theme", tn("modPartTheme"))}${box("identity", tn("modPartIdentity"))}${box("header", tn("modPartWidget_chat_header"))}${box("details", tn("modPartWidget_details"))}${box("sidebar", tn("modPartWidget_sidebar"))}${box("ownerCss", tn("modPartCss"))}</div>
|
|
198
|
+
<div class="s-mod-boxes">${box("theme", tn("modPartTheme"))}${box("identity", tn("modPartIdentity"))}${box("header", tn("modPartWidget_chat_header"))}${box("details", tn("modPartWidget_details"))}${box("ambient", tn("modPartAmbient"))}${box("sidebar", tn("modPartWidget_sidebar"))}${box("ownerCss", tn("modPartCss"))}</div>
|
|
186
199
|
<label class="field"><span>${tn("modExportName")}</span><input value=${f.name} placeholder="my-look" onInput=${set("name")} /></label>
|
|
187
200
|
<label class="field"><span>${tn("modExportVersion")}</span><input value=${f.version} onInput=${set("version")} /></label>
|
|
188
201
|
<label class="field"><span>${tn("modExportTitle")}</span><input value=${f.title} onInput=${set("title")} /></label>
|
|
@@ -13,7 +13,7 @@ register("ownerEffects", {
|
|
|
13
13
|
unknown: "Choices are unavailable. Reload to try again.", changed: "This page can no longer access these choices. Reload and sign in again.",
|
|
14
14
|
unavailable: "No longer available", clear: "Clear", reload: "Reload choices", saved: "Saved.", failed: "Couldn't save. Reload choices and try again.",
|
|
15
15
|
savedRefresh: "Saved, but the choices couldn't be refreshed. Reload to check.", readOnly: "Sign in as an admin to change these choices.",
|
|
16
|
-
playback: "
|
|
16
|
+
playback: "Effects don't require HTML-preview permission.",
|
|
17
17
|
pause: "Pause", resume: "Resume", running: "Running", cursorFailed: "The cursor could not be applied here. Clear or reload the choice to try again.", starting: "Starting", queued: "Waiting for a slot", paused: "Paused", off: "Playback is off or unavailable here.", budget: "Paused to keep the page responsive.", resumeFailed: "This effect cannot resume: the page or its safety history is not ready. Reload the page or choose another effect.",
|
|
18
18
|
},
|
|
19
19
|
"zh-TW": {
|
|
@@ -23,7 +23,7 @@ register("ownerEffects", {
|
|
|
23
23
|
unknown: "目前無法取得選擇,請重新載入再試。", changed: "這個頁面已無法存取選擇,請重新載入並登入。",
|
|
24
24
|
unavailable: "已無法使用", clear: "清除", reload: "重新載入選擇", saved: "已儲存。", failed: "無法儲存,請重新載入選擇再試。",
|
|
25
25
|
savedRefresh: "已儲存,但無法更新畫面;請重新載入確認。", readOnly: "請以管理員身分登入才能修改選擇。",
|
|
26
|
-
playback: "
|
|
26
|
+
playback: "效果不需要 HTML 預覽權限。",
|
|
27
27
|
pause: "暫停", resume: "恢復", running: "播放中", cursorFailed: "這裡無法套用游標。可清除或重新載入選擇後再試。", starting: "啟動中", queued: "等待空位", paused: "已暫停", off: "這裡已關閉播放,或無法播放效果。", budget: "為維持頁面順暢,已暫停。", resumeFailed: "頁面或安全紀錄尚未就緒,無法恢復此效果。請重新載入頁面,或選擇其他效果。",
|
|
28
28
|
},
|
|
29
29
|
});
|
|
@@ -39,7 +39,7 @@ export function OwnerEffects({ catalog, busy = false }) {
|
|
|
39
39
|
useEffect(() => { life.current.alive = true; const stop = watchOwnerEffects(); return () => { life.current.alive = false; life.current.seq++; stop(); }; }, []);
|
|
40
40
|
useEffect(() => {
|
|
41
41
|
let live = true; const entry = getAppEffects();
|
|
42
|
-
const pull = () => { if (!live) return; const gates = entry?.gates(); setRuntime({ entry, snapshot: entry?.api?.ownerSnapshot?.(), cursor: entry?.cursorSnapshot?.(), available: !!entry && !entry.dead && gates.enabled && gates.show && gates.
|
|
42
|
+
const pull = () => { if (!live) return; const gates = entry?.gates(); setRuntime({ entry, snapshot: entry?.api?.ownerSnapshot?.(), cursor: entry?.cursorSnapshot?.(), available: !!entry && !entry.dead && gates.enabled && gates.show && gates.transportReady && !gates.safe && !gates.publicLink && !gates.reducedMotion && !gates.saveData }); };
|
|
43
43
|
const off = entry?.subscribe(pull), end = entry?.onEnd(pull); pull();
|
|
44
44
|
return () => { live = false; off?.(); end?.(); };
|
|
45
45
|
}, []);
|
|
@@ -316,6 +316,8 @@ register("settings", {
|
|
|
316
316
|
themesTitle: "Agents' themes",
|
|
317
317
|
themesShow: "Show agents' themes, effects and widgets",
|
|
318
318
|
themesShowLead: "When off, every chat looks the same.",
|
|
319
|
+
themesFollowMotion: "Effects: follow the system's reduced-motion setting",
|
|
320
|
+
themesFollowMotionLead: "This device only: effects stay still while the system reduces motion.",
|
|
319
321
|
themesSafeMode: "This page was opened with ?safe=1: agents' themes are not shown on it.",
|
|
320
322
|
widgetTitle: "Your sidebar widget",
|
|
321
323
|
widgetLead: "A small panel under your agent list, written as JSON.",
|
|
@@ -337,6 +339,7 @@ register("settings", {
|
|
|
337
339
|
modReplace: "Replace the installed version {0}",
|
|
338
340
|
modPartTheme: "Theme (colours, font, pictures)",
|
|
339
341
|
modPartIdentity: "Card text (tagline and bio)",
|
|
342
|
+
modPartAmbient: "Ambient effect (falling, rising, twinkling or click glyphs)",
|
|
340
343
|
modPartWidget_chat_header: "Chat header widget",
|
|
341
344
|
modPartWidget_details: "Details page widget",
|
|
342
345
|
modPartWidget_sidebar: "Your sidebar widget",
|
|
@@ -439,6 +442,17 @@ register("settings", {
|
|
|
439
442
|
guestNarrow: "Narrow", guestNotReachable: "(no longer reachable)", guestReachesNothing: "This account reaches no agent from chat right now, so there is nothing to narrow.", guestNarrowLift: "Remove narrowing", guestNarrowHint: "Tick what this guest may still reach. Narrowing only takes away (it applies at once); widening or removing it waits for a fleet admin's confirmation.",
|
|
440
443
|
guestSignOut: "Sign out", guestSignOutTitle: "Sign out this guest?", guestSignOutConfirm: "{0}'s guest sessions end now, their sign-in code stops working and their browsers are forgotten. They can sign in again with /web while guest sign-in is on.",
|
|
441
444
|
guestSignedOut: "{0} is signed out.", guestSignedOutNotDurable: "{0} is signed out for now, but it could not be saved: a restart may bring their sessions back.", guestSignOutFailed: "That did not work — try again.",
|
|
445
|
+
vaTitle: "Web authority", vaReview: "Review web authority changes",
|
|
446
|
+
vaHint: "Whether a signed-in fleet admin's web messages carry their own account's authority, recorded in each message's delivery receipt. A session signed in with a code sent privately to that account, on the dashboard reached directly, is authorized once from Discord or Telegram; the public link and guests never are. Agents don't act on it yet: they still treat web messages as recognition only.",
|
|
447
|
+
vaLoadFailed: "Web authority could not be loaded.",
|
|
448
|
+
vaEnabled: "Web messages carry authority", vaEnabledHint: "On by default: a session carries authority only once its account confirms it from Discord or Telegram. Off: no web message carries authority, and any already given reads as none at once; authority given before it was turned off never comes back.",
|
|
449
|
+
vaStuckOff: "Web authority is off for now: its state could not be saved on this computer (see the fleet log). It comes back on by itself once it can be.",
|
|
450
|
+
vaLoopbackOnly: "Only this computer can authorize", vaLoopbackOnlyHint: "On: only a browser on this computer qualifies, not one on your network or tailnet, and authority already given from elsewhere reads as none. Turning it off again doesn't bring that back.",
|
|
451
|
+
vaLanWarning: "The dashboard also answers to {0} (web.allowed_hosts). Anyone who reaches it there with a stolen sign-in could give authority in your name. Turn on \"Only this computer can authorize\" unless you trust everyone who can reach it.",
|
|
452
|
+
vaHours: "An authorization lasts (hours, {0}–{1})", vaHoursRange: "Must be a number from 1 to 48",
|
|
453
|
+
vaSessionsTitle: "Authorized sessions", vaSessionsNone: "No session is authorized now.", vaSessionUntil: "Authorized {0} · until {1}",
|
|
454
|
+
vaRevoke: "Revoke web authority", vaRevokeTitle: "Revoke all web authority?", vaRevokeConfirm: "Every authority given from the web so far reads as none from now on, and every session's authorization ends. Nobody is signed out: a session carries authority again once it is authorized again.",
|
|
455
|
+
vaRevoked: "Web authority given so far is revoked.", vaRevokedNotDurable: "Revoked for now, but it could not be saved: web authority stays off until it can be, and a restart before then undoes the revocation.", vaRevokeFailed: "That did not work — try again.",
|
|
442
456
|
webReadOnlyLead: "Read-only here: these decide what the dashboard listens on and exposes. Edit fleet.yaml, then run agend reload (web.preview_port and web.preview_origin take a restart of AgEnD).",
|
|
443
457
|
notSet: "not set (default)",
|
|
444
458
|
defaultsTitle: "Defaults",
|
|
@@ -823,6 +837,8 @@ register("settings", {
|
|
|
823
837
|
themesTitle: "Agent 的主題",
|
|
824
838
|
themesShow: "顯示 agent 的主題、效果與小工具",
|
|
825
839
|
themesShowLead: "關閉時每個聊天看起來都一樣。",
|
|
840
|
+
themesFollowMotion: "效果:跟隨系統的「減少動態效果」設定",
|
|
841
|
+
themesFollowMotionLead: "只套用於這台裝置:系統減少動態效果時,效果保持靜止。",
|
|
826
842
|
themesSafeMode: "這個頁面是用 ?safe=1 開啟的:不顯示 agent 的主題。",
|
|
827
843
|
widgetTitle: "你的側邊欄小工具",
|
|
828
844
|
widgetLead: "agent 清單下方的小面板,用 JSON 撰寫。",
|
|
@@ -844,6 +860,7 @@ register("settings", {
|
|
|
844
860
|
modReplace: "取代已安裝的 {0} 版",
|
|
845
861
|
modPartTheme: "主題(顏色、字型、圖片)",
|
|
846
862
|
modPartIdentity: "身分卡文字(標語與簡介)",
|
|
863
|
+
modPartAmbient: "環境效果(飄落、上升、閃爍或點擊符號)",
|
|
847
864
|
modPartWidget_chat_header: "聊天標頭小工具",
|
|
848
865
|
modPartWidget_details: "詳細資料頁小工具",
|
|
849
866
|
modPartWidget_sidebar: "你的側邊欄小工具",
|
|
@@ -946,6 +963,17 @@ register("settings", {
|
|
|
946
963
|
guestNarrow: "縮小範圍", guestNotReachable: "(已無法使用)", guestReachesNothing: "這個帳號目前在聊天中沒有能用的 agent,沒有可以縮小的範圍。", guestNarrowLift: "取消縮小", guestNarrowHint: "勾選這位訪客仍可使用的 agent。縮小只會拿走權限(立即生效);放寬或取消縮小需 fleet admin 確認。",
|
|
947
964
|
guestSignOut: "登出", guestSignOutTitle: "登出這位訪客?", guestSignOutConfirm: "{0} 的訪客 session 會立即結束,登入碼失效,記住的瀏覽器也會被忘記。訪客登入開啟期間,他可以再傳 /web 登入。",
|
|
948
965
|
guestSignedOut: "已登出 {0}。", guestSignedOutNotDurable: "已暫時登出 {0},但無法保存:重新啟動後他的 session 可能會恢復。", guestSignOutFailed: "沒有成功,請再試一次。",
|
|
966
|
+
vaTitle: "網頁授權", vaReview: "檢視網頁授權變更",
|
|
967
|
+
vaHint: "已登入的 fleet 管理者從網頁送出的訊息,是否帶有他本人帳號的授權,記錄在每則訊息的送達收據中。用私下傳給該帳號的登入碼、透過直接連線的 dashboard 登入的 session,要在 Discord 或 Telegram 上授權一次;公開連結與訪客一律不行。目前 agent 還不會依它行動:仍只把網頁訊息當成辨識。",
|
|
968
|
+
vaLoadFailed: "無法載入網頁授權。",
|
|
969
|
+
vaEnabled: "網頁訊息帶有授權", vaEnabledHint: "預設開啟:session 要等它的帳號在 Discord 或 Telegram 上確認後才帶有授權。關閉:網頁訊息都不帶授權,已給出的授權立即視為無;關閉前給出的授權不會恢復。",
|
|
970
|
+
vaStuckOff: "網頁授權目前是關閉的:它的狀態無法存到這台電腦上(請看 fleet 紀錄)。可以存入後會自動恢復。",
|
|
971
|
+
vaLoopbackOnly: "只有這台電腦可以授權", vaLoopbackOnlyHint: "開啟:只有這台電腦上的瀏覽器符合,網路或 tailnet 上的不算,之前從其他地方給出的授權視為無。再關掉也不會讓那些授權恢復。",
|
|
972
|
+
vaLanWarning: "dashboard 也接受 {0} 的連線(web.allowed_hosts)。任何人只要從那裡連上、又拿到被盜的登入,就能以你的名義給出授權。除非你信任所有能連到那裡的人,否則請開啟「只有這台電腦可以授權」。",
|
|
973
|
+
vaHours: "一次授權的有效期(小時,{0}–{1})", vaHoursRange: "必須是 1 到 48 之間的數字",
|
|
974
|
+
vaSessionsTitle: "已授權的 session", vaSessionsNone: "目前沒有已授權的 session。", vaSessionUntil: "{0} 授權 · 到 {1}",
|
|
975
|
+
vaRevoke: "撤銷網頁授權", vaRevokeTitle: "撤銷所有網頁授權?", vaRevokeConfirm: "到目前為止從網頁給出的授權,從現在起都視為無,所有 session 的授權也會結束。不會登出任何人:session 再授權一次後,送出的訊息會再帶有授權。",
|
|
976
|
+
vaRevoked: "已撤銷到目前為止的網頁授權。", vaRevokedNotDurable: "已暫時撤銷,但無法保存:在可以保存之前網頁授權會保持關閉,而在那之前重新啟動會讓這次撤銷失效。", vaRevokeFailed: "沒有成功,請再試一次。",
|
|
949
977
|
webReadOnlyLead: "這裡僅供檢視:這些設定決定儀表板監聽和公開的內容。請編輯 fleet.yaml 後執行 agend reload(web.preview_port 與 web.preview_origin 需要重新啟動 AgEnD)。",
|
|
950
978
|
notSet: "未設定(預設)",
|
|
951
979
|
defaultsTitle: "預設值",
|