@songsid/agend 2.3.0-beta.21 → 2.3.0-beta.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/README.zh-TW.md +2 -2
- package/dist/agent-cli.js +4 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +9 -0
- package/dist/agent-endpoint.js +15 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +5 -0
- package/dist/agent-whats-new.js +16 -1
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +7 -0
- package/dist/auth-api.js +10 -3
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/codex-package.d.ts +30 -0
- package/dist/backend/codex-package.js +133 -0
- package/dist/backend/codex-package.js.map +1 -0
- package/dist/backend/codex.d.ts +2 -0
- package/dist/backend/codex.js +9 -2
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.js +56 -3
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +9 -1
- package/dist/backend/kiro.js +102 -1
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/opencode-permission.js +1 -1
- package/dist/backend/opencode-permission.js.map +1 -1
- package/dist/backend/opencode.js +2 -1
- package/dist/backend/opencode.js.map +1 -1
- package/dist/binary-probe.js +1 -1
- package/dist/bug-report-cli.js +1 -1
- package/dist/bundled-skills.d.ts +7 -3
- package/dist/bundled-skills.js +16 -7
- package/dist/bundled-skills.js.map +1 -1
- package/dist/channel/adapters/discord.js +3 -3
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-server.js +12 -4
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +3 -1
- package/dist/channel/mcp-tools.js +38 -2
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/child-process.d.ts +21 -0
- package/dist/child-process.js +54 -0
- package/dist/child-process.js.map +1 -0
- package/dist/cli.js +167 -182
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +1 -1
- package/dist/command-table.js +1 -1
- package/dist/command-table.js.map +1 -1
- package/dist/config-validator.js +28 -2
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +16 -2
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +14 -0
- package/dist/daemon.js +45 -8
- package/dist/daemon.js.map +1 -1
- package/dist/darwin-memory.js +1 -1
- package/dist/doctor.d.ts +5 -0
- package/dist/doctor.js +42 -0
- package/dist/doctor.js.map +1 -1
- package/dist/export-import.js +1 -1
- package/dist/export-import.js.map +1 -1
- package/dist/fleet-level-config.d.ts +1 -1
- package/dist/fleet-level-config.js +2 -0
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +73 -2
- package/dist/fleet-manager.js +400 -33
- package/dist/fleet-manager.js.map +1 -1
- package/dist/full-restart.d.ts +5 -5
- package/dist/full-restart.js +5 -5
- package/dist/full-restart.js.map +1 -1
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +1 -1
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +8 -9
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +319 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +529 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +19 -4
- package/dist/general-knowledge/steering/core-rules.md +1 -1
- package/dist/instance-config-impact.js +13 -3
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.js +6 -6
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.js +2 -1
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +137 -10
- package/dist/locale.js.map +1 -1
- package/dist/login-flows.js +1 -1
- package/dist/login-shell.js +3 -1
- package/dist/login-shell.js.map +1 -1
- package/dist/ls-rows.js +1 -1
- package/dist/needs-you-hub.d.ts +98 -1
- package/dist/needs-you-hub.js +313 -44
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +5 -2
- package/dist/needs-you-live.js +6 -3
- package/dist/needs-you-live.js.map +1 -1
- package/dist/needs-you.d.ts +58 -3
- package/dist/needs-you.js +36 -3
- package/dist/needs-you.js.map +1 -1
- package/dist/outbound-handlers.js +25 -10
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +3 -3
- package/dist/provider-secret-registry.d.ts +5 -3
- package/dist/provider-secret-registry.js +8 -2
- package/dist/provider-secret-registry.js.map +1 -1
- package/dist/public-link-progress.d.ts +12 -1
- package/dist/public-link-progress.js +5 -10
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-tunnel-config.d.ts +11 -0
- package/dist/public-tunnel-config.js +68 -0
- package/dist/public-tunnel-config.js.map +1 -0
- package/dist/public-web-gateway.d.ts +2 -0
- package/dist/public-web-gateway.js +3 -2
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +13 -2
- package/dist/public-web-link.js +37 -10
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart.js +14 -8
- package/dist/quickstart.js.map +1 -1
- package/dist/resource-report.js +1 -1
- package/dist/scheduler/task-board.d.ts +41 -1
- package/dist/scheduler/task-board.js +150 -4
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/sd-notify.js +2 -1
- package/dist/sd-notify.js.map +1 -1
- package/dist/secret-store.d.ts +2 -0
- package/dist/secret-store.js +21 -0
- package/dist/secret-store.js.map +1 -1
- package/dist/service-installer.d.ts +69 -0
- package/dist/service-installer.js +181 -0
- package/dist/service-installer.js.map +1 -1
- package/dist/service-owner.d.ts +44 -0
- package/dist/service-owner.js +48 -0
- package/dist/service-owner.js.map +1 -0
- package/dist/settings-api.d.ts +5 -0
- package/dist/settings-api.js +32 -10
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +86 -3
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +6 -1
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.d.ts +2 -0
- package/dist/settings-http-confirmation.js +4 -3
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-request-capability.d.ts +4 -1
- package/dist/settings-request-capability.js +11 -3
- package/dist/settings-request-capability.js.map +1 -1
- package/dist/setup-host.js +1 -1
- package/dist/setup-wizard.js +1 -1
- package/dist/setup-wizard.js.map +1 -1
- package/dist/task-board-summary.d.ts +101 -0
- package/dist/task-board-summary.js +177 -0
- package/dist/task-board-summary.js.map +1 -0
- package/dist/task-board-waker.d.ts +8 -1
- package/dist/task-board-waker.js +15 -6
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tmux-control.js +1 -1
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-exec-helper.d.ts +5 -1
- package/dist/tmux-exec-helper.js +13 -21
- package/dist/tmux-exec-helper.js.map +1 -1
- package/dist/tmux-manager.js +1 -1
- package/dist/tmux-read.js +1 -1
- package/dist/tmux-read.js.map +1 -1
- package/dist/topic-commands.d.ts +3 -0
- package/dist/topic-commands.js +29 -18
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.js +5 -0
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +2 -0
- package/dist/transcript-sources.js +4 -0
- package/dist/transcript-sources.js.map +1 -1
- package/dist/tunnel/cloudflared-install.js +3 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +7 -16
- package/dist/tunnel/cloudflared.js +65 -13
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/named-diagnostics.d.ts +13 -0
- package/dist/tunnel/named-diagnostics.js +52 -0
- package/dist/tunnel/named-diagnostics.js.map +1 -0
- package/dist/tunnel/types.d.ts +2 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/ui/app-effects.js +10 -4
- package/dist/ui/app.html +2 -0
- package/dist/ui/chat-render.js +39 -0
- package/dist/ui/chat-store.js +8 -0
- package/dist/ui/chat-strings.js +5 -2
- package/dist/ui/chat-thread.js +15 -8
- package/dist/ui/effect-trial-offer.js +2 -31
- package/dist/ui/effects-waker.js +2 -70
- package/dist/ui/fleet-canvas.js +14 -6
- package/dist/ui/fleet-route-scene.js +86 -21
- package/dist/ui/fleet-task-card.js +5 -5
- package/dist/ui/fleet-task-common.js +41 -7
- package/dist/ui/fleet-task-work.js +107 -0
- package/dist/ui/fleet-tasks.js +46 -12
- package/dist/ui/fleet-traffic.js +57 -10
- package/dist/ui/icons/icon-192.png +0 -0
- package/dist/ui/icons/icon-512.png +0 -0
- package/dist/ui/image-lightbox.js +39 -1
- package/dist/ui/image-zoom.js +242 -0
- package/dist/ui/panel-chat.js +24 -13
- package/dist/ui/panel-details.js +2 -0
- package/dist/ui/panel-fleet.js +6 -2
- package/dist/ui/panel-needs.js +21 -6
- package/dist/ui/panel-settings.js +90 -15
- package/dist/ui/preview-panel.js +2 -1
- package/dist/ui/search-dialog.js +2 -1
- package/dist/ui/settings-appearance.js +159 -9
- package/dist/ui/settings-dialogs.js +33 -17
- package/dist/ui/settings-discord.js +6 -2
- package/dist/ui/settings-guests.js +2 -1
- package/dist/ui/settings-learn.js +1 -0
- package/dist/ui/settings-model.js +2 -11
- package/dist/ui/settings-mods.js +6 -3
- package/dist/ui/settings-strings.js +80 -0
- package/dist/ui/settings-token.js +3 -1
- package/dist/ui/settings-web-authority.js +6 -3
- package/dist/ui/settings-wizard.js +13 -7
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-needs.js +2 -2
- package/dist/ui/shared/app-store.js +2 -2
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +125 -28
- package/dist/ui/shared/app.js +5 -1
- package/dist/ui/shared/effect-app-runtime.js +7 -5
- package/dist/ui/shared/effect-chat-runtime.js +2 -7
- package/dist/ui/shared/effect-cursor.js +3 -2
- package/dist/ui/shared/effect-display-metadata.js +39 -0
- package/dist/ui/shared/effect-registry.js +4 -2
- package/dist/ui/shared/effect-runtime-loader.js +3 -1
- package/dist/ui/shared/effect-stock-trust.js +1 -1
- package/dist/ui/shared/feature-flags.js +127 -0
- package/dist/ui/shared/identity-color.js +6 -3
- package/dist/ui/shared/instance-nav.js +3 -2
- package/dist/ui/shared/text-undo.js +210 -0
- package/dist/ui/shared/ui-dialog.js +4 -3
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/signin.html +2 -0
- package/dist/update-dispatch.js +1 -1
- package/dist/update-launch.d.ts +1 -1
- package/dist/update-launch.js +1 -2
- package/dist/update-launch.js.map +1 -1
- package/dist/usage/providers.js +1 -1
- package/dist/usage/providers.js.map +1 -1
- package/dist/view-api.js +1 -1
- package/dist/web-api.d.ts +12 -2
- package/dist/web-api.js +266 -18
- package/dist/web-api.js.map +1 -1
- package/dist/web-app-icon-worker.d.ts +1 -0
- package/dist/web-app-icon-worker.js +15 -0
- package/dist/web-app-icon-worker.js.map +1 -0
- package/dist/web-app-icon.d.ts +107 -0
- package/dist/web-app-icon.js +256 -0
- package/dist/web-app-icon.js.map +1 -0
- package/dist/web-appearance-tools.d.ts +1 -0
- package/dist/web-appearance-tools.js +7 -3
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance.d.ts +4 -10
- package/dist/web-appearance.js +4 -3
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-chat-history.d.ts +37 -0
- package/dist/web-chat-history.js +57 -2
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-experimental-http.d.ts +2 -0
- package/dist/web-experimental-http.js +12 -0
- package/dist/web-experimental-http.js.map +1 -0
- package/dist/web-experimental.d.ts +11 -0
- package/dist/web-experimental.js +49 -0
- package/dist/web-experimental.js.map +1 -0
- package/dist/web-guest-access.d.ts +1 -0
- package/dist/web-guest-access.js +11 -3
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-icons.d.ts +33 -1
- package/dist/web-icons.js +83 -11
- package/dist/web-icons.js.map +1 -1
- package/dist/web-mod-store.d.ts +2 -0
- package/dist/web-mod-store.js +10 -0
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-png.d.ts +29 -0
- package/dist/web-png.js +321 -0
- package/dist/web-png.js.map +1 -0
- package/dist/web-preview.d.ts +2 -0
- package/dist/web-preview.js +2 -0
- package/dist/web-preview.js.map +1 -1
- package/dist/web-reactions.d.ts +34 -0
- package/dist/web-reactions.js +47 -0
- package/dist/web-reactions.js.map +1 -0
- package/dist/web-settings-patch.d.ts +2 -1
- package/dist/web-settings-patch.js +5 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +1 -0
- package/dist/web-settings-tools.js +14 -10
- package/dist/web-settings-tools.js.map +1 -1
- package/dist/web-terminal.js +1 -1
- package/dist/web-theme-store.d.ts +2 -2
- package/dist/web-theme-store.js +6 -3
- package/dist/web-theme-store.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
// versions, the pictures and fonts it may name, and this device's off switch. Settings itself is always drawn stock
|
|
3
3
|
// (owner-css.js removes the link here), so a stylesheet that hides things can always be fixed from this page.
|
|
4
4
|
import { html, useEffect, useRef, useState } from "/assets/app-html.js";
|
|
5
|
-
import { t } from "/assets/app-i18n.js";
|
|
5
|
+
import { register, t } from "/assets/app-i18n.js";
|
|
6
|
+
import { recordEdit } from "/assets/text-undo.js";
|
|
6
7
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
7
8
|
import { ModsCard } from "./settings-mods.js";
|
|
8
|
-
import {
|
|
9
|
+
import { experimentalComponent, useCanvasWorker } from "/assets/feature-flags.js";
|
|
10
|
+
const EffectsCard = experimentalComponent(() => import("./settings-effects.js"), m => m.EffectsCard);
|
|
9
11
|
import { Icon } from "/assets/ui-icons.js";
|
|
10
12
|
import { toast } from "/assets/ui-toast.js";
|
|
11
13
|
import { ownerCssOffHere, ownerCssTrial, safeMode, setOwnerCssActive, setOwnerCssOffHere, startOwnerCssTrial } from "/assets/owner-css.js";
|
|
@@ -45,11 +47,30 @@ export function AppearanceSection() {
|
|
|
45
47
|
const publicLink = typeof document !== "undefined" && document.body && document.body.dataset.publicLink === "1";
|
|
46
48
|
if (publicLink) return html`<section class="card s-appearance"><h3>${tn("cssTitle")}</h3><p class="sub">${tn("cssPublicLink")}</p></section>`;
|
|
47
49
|
const group = (key) => html`<h2 class="s-ap-group">${tn(key)}</h2>`;
|
|
48
|
-
return html`${group("groupLook")}<${ThemesCard} /><${IdentityColorCard} /><${CardSizeCard} />
|
|
50
|
+
return html`${group("groupLook")}<${ThemesCard} /><${IdentityColorCard} /><${CardSizeCard} /><${AppIconCard} />
|
|
49
51
|
${group("groupLayout")}<${SidebarWidgetCard} />
|
|
50
52
|
${group("groupCss")}<${OwnerCssCard} /><${DeviceCard} />
|
|
51
53
|
${group("groupMods")}<${ModsCard} /><${EffectsCard} />
|
|
52
|
-
${group("groupAdvanced")}<${CardLimitsCard} /><${RestoreDefaultsCard} />`;
|
|
54
|
+
${group("groupAdvanced")}<${ExperimentalEffectsCard} /><${CardLimitsCard} /><${RestoreDefaultsCard} />`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
register("experimentalEffects", {
|
|
58
|
+
en: { title: "Experimental: Canvas Worker effects", help: "Enable uploaded Canvas Worker effects. Ambient effects are always available. Saved effects stay intact when this is off.", failed: "Could not change this setting. Try again." },
|
|
59
|
+
"zh-TW": { title: "實驗功能:Canvas Worker 特效", help: "啟用上傳的 Canvas Worker 特效。氛圍特效不受影響;關閉時保留已儲存的特效。", failed: "無法變更此設定,請重試。" },
|
|
60
|
+
});
|
|
61
|
+
function ExperimentalEffectsCard() {
|
|
62
|
+
const enabled = useCanvasWorker(), [writable, setWritable] = useState(false), [busy, setBusy] = useState(false), [failed, setFailed] = useState(false);
|
|
63
|
+
useEffect(() => { let live = true; Promise.resolve(globalThis.AgendAuth?.trialSession?.()).then(value => { if (live) setWritable(value?.localAdmin === true); }, () => {}); return () => { live = false; }; }, []);
|
|
64
|
+
if (!writable) return null;
|
|
65
|
+
const change = async value => {
|
|
66
|
+
setBusy(true); setFailed(false);
|
|
67
|
+
try {
|
|
68
|
+
const result = await confirmedWrite("/api/settings/fleet/web", { method: "PUT", body: { experimental: { canvas_worker: value } }, label: t("experimentalEffects.title") });
|
|
69
|
+
if (result.ok) appStore.set({ canvasWorker: value }); else setFailed(true);
|
|
70
|
+
} catch { setFailed(true); }
|
|
71
|
+
finally { setBusy(false); }
|
|
72
|
+
};
|
|
73
|
+
return html`<section class="card s-appearance s-experimental-effects"><label class="check"><input type="checkbox" checked=${enabled} disabled=${busy} onChange=${event => change(event.target.checked)} /><span>${t("experimentalEffects.title")}</span></label><p class="sub">${t("experimentalEffects.help")}</p>${failed ? html`<p role="alert">${t("experimentalEffects.failed")}</p>` : null}</section>`;
|
|
53
74
|
}
|
|
54
75
|
|
|
55
76
|
/** #2056 B: a sample sidebar row in the host's default colour places, so the choice is seen rather than described. */
|
|
@@ -127,6 +148,131 @@ function CardSizeCard() {
|
|
|
127
148
|
</section>`;
|
|
128
149
|
}
|
|
129
150
|
|
|
151
|
+
/**
|
|
152
|
+
* #2308: the image as one square PNG the host makes the icon sizes from — centre-cropped, drawn at 512 px if smaller
|
|
153
|
+
* (marked low-resolution), as it is up to 1024 px, else 1024. The browser decodes whatever it can show (PNG, JPG, WebP);
|
|
154
|
+
* the host takes only that PNG.
|
|
155
|
+
*/
|
|
156
|
+
export async function appIconPng(file) {
|
|
157
|
+
const bmp = await createImageBitmap(file);
|
|
158
|
+
try {
|
|
159
|
+
const side = Math.min(bmp.width, bmp.height);
|
|
160
|
+
const out = Math.min(1024, Math.max(512, side));
|
|
161
|
+
const canvas = document.createElement("canvas");
|
|
162
|
+
canvas.width = canvas.height = out;
|
|
163
|
+
const g = canvas.getContext("2d");
|
|
164
|
+
g.imageSmoothingQuality = "high";
|
|
165
|
+
g.drawImage(bmp, (bmp.width - side) / 2, (bmp.height - side) / 2, side, side, 0, 0, out, out);
|
|
166
|
+
const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
|
|
167
|
+
if (!blob) throw new Error("no PNG");
|
|
168
|
+
return { blob, low: side < 512 };
|
|
169
|
+
} finally {
|
|
170
|
+
if (bmp.close) bmp.close();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** #2308: this page's icon links follow a change at once (the next pages carry the version from the host). */
|
|
175
|
+
export function refreshAppIconLinks(version, custom) {
|
|
176
|
+
if (typeof document === "undefined") return;
|
|
177
|
+
const v = encodeURIComponent(version);
|
|
178
|
+
const head = document.head;
|
|
179
|
+
const links = [...head.querySelectorAll("link")];
|
|
180
|
+
const byPath = (path) => links.find((l) => (l.getAttribute("href") || "").split("?")[0] === path);
|
|
181
|
+
for (const [path, rel] of [["/favicon.ico", "icon"], ["/apple-touch-icon.png", "apple-touch-icon"], ["/manifest.webmanifest", "manifest"]]) {
|
|
182
|
+
const el = byPath(path);
|
|
183
|
+
if (el && el.getAttribute("rel") === rel) el.setAttribute("href", `${path}?v=${v}`);
|
|
184
|
+
}
|
|
185
|
+
const svg = byPath("/favicon.svg");
|
|
186
|
+
if (custom && svg) svg.remove();
|
|
187
|
+
else if (!custom && !svg) {
|
|
188
|
+
const link = document.createElement("link");
|
|
189
|
+
for (const [k, val] of [["rel", "icon"], ["href", `/favicon.svg?v=${v}`], ["type", "image/svg+xml"], ["sizes", "any"]]) link.setAttribute(k, val);
|
|
190
|
+
head.appendChild(link);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* #2308: the app icon — what a browser tab and a home screen show. One picture for every page (sign-in and the public
|
|
196
|
+
* link too); an admin changes it, everyone else sees it.
|
|
197
|
+
*/
|
|
198
|
+
function AppIconCard() {
|
|
199
|
+
const [state, setState] = useState(null);
|
|
200
|
+
const [canWrite, setCanWrite] = useState(false);
|
|
201
|
+
const [picked, setPicked] = useState(null); // { blob, url, low }
|
|
202
|
+
const [busy, setBusy] = useState(false);
|
|
203
|
+
const [error, setError] = useState("");
|
|
204
|
+
const file = useRef(null);
|
|
205
|
+
const load = async () => {
|
|
206
|
+
const r = await call("/ui/app-icon");
|
|
207
|
+
setState(r.ok && r.body && typeof r.body.version === "string" ? r.body : { failed: true });
|
|
208
|
+
};
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
load();
|
|
211
|
+
const auth = typeof window !== "undefined" ? window.AgendAuth : null;
|
|
212
|
+
if (auth && auth.canWrite) Promise.resolve(auth.canWrite()).then((v) => setCanWrite(v === true), () => {});
|
|
213
|
+
}, []);
|
|
214
|
+
useEffect(() => () => { if (picked) URL.revokeObjectURL(picked.url); }, [picked]);
|
|
215
|
+
const title = html`<${CardTitle} title=${tn("appIconTitle")} topic="app-icon" />`;
|
|
216
|
+
if (!state) return html`<section class="card s-appearance s-app-icon">${title}<p class="sub">…</p></section>`;
|
|
217
|
+
if (state.failed) return html`<section class="card s-appearance s-app-icon">${title}<p class="err">${tn("appIconLoadFailed")}</p></section>`;
|
|
218
|
+
|
|
219
|
+
const choose = async () => {
|
|
220
|
+
const f = file.current && file.current.files && file.current.files[0];
|
|
221
|
+
if (file.current) file.current.value = "";
|
|
222
|
+
if (!f) return;
|
|
223
|
+
setError("");
|
|
224
|
+
try {
|
|
225
|
+
const made = await appIconPng(f);
|
|
226
|
+
setPicked({ ...made, url: URL.createObjectURL(made.blob) });
|
|
227
|
+
} catch {
|
|
228
|
+
setError(tn("appIconNotAnImage"));
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
const answered = (r, done) => {
|
|
232
|
+
setBusy(false);
|
|
233
|
+
if (!r.ok) {
|
|
234
|
+
const known = ["app_icon_too_large", "app_icon_not_png", "app_icon_unreadable", "app_icon_admin_only", "app_icon_needs_a_signed_in_person", "app_icon_busy", "app_icon_superseded", "app_icon_not_reset"];
|
|
235
|
+
setError(known.includes(r.body && r.body.error) ? tn(`appIconError_${r.body.error}`, (r.body && r.body.detail) || "") : tn("appIconError_other", (r.body && r.body.error) || String(r.status)));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
setState(r.body);
|
|
239
|
+
setPicked(null);
|
|
240
|
+
refreshAppIconLinks(r.body.version, r.body.custom);
|
|
241
|
+
toast(done);
|
|
242
|
+
};
|
|
243
|
+
const use = async () => {
|
|
244
|
+
setBusy(true); setError("");
|
|
245
|
+
answered(await call("/ui/app-icon", { method: "PUT", headers: { "Content-Type": "image/png" }, body: picked.blob }), tn("appIconSaved"));
|
|
246
|
+
};
|
|
247
|
+
const reset = async () => {
|
|
248
|
+
setBusy(true); setError("");
|
|
249
|
+
answered(await call("/ui/app-icon", { method: "DELETE" }), tn("appIconResetDone"));
|
|
250
|
+
};
|
|
251
|
+
const v = encodeURIComponent(state.version);
|
|
252
|
+
return html`<section class="card s-appearance s-app-icon">${title}
|
|
253
|
+
<p class="sub">${tn("appIconLead")}</p>
|
|
254
|
+
<div class="s-app-icon-row">
|
|
255
|
+
<figure class="s-app-icon-now"><img class="s-app-icon-big" src=${`/apple-touch-icon.png?v=${v}`} width="96" height="96" alt=${tn("appIconCurrent")} />
|
|
256
|
+
<img class="s-app-icon-small" src=${`/icon-192.png?v=${v}`} width="32" height="32" alt="" />
|
|
257
|
+
<figcaption>${state.custom ? tn("appIconCustom") : tn("appIconDefault")}</figcaption></figure>
|
|
258
|
+
${picked ? html`<figure class="s-app-icon-new"><img class="s-app-icon-big" src=${picked.url} width="96" height="96" alt=${tn("appIconPreview")} />
|
|
259
|
+
<figcaption>${tn("appIconPreview")}</figcaption></figure>` : null}
|
|
260
|
+
</div>
|
|
261
|
+
${picked && picked.low ? html`<p class="sub s-app-icon-low">${tn("appIconLow")}</p>` : null}
|
|
262
|
+
${error ? html`<p class="err" role="alert">${error}</p>` : null}
|
|
263
|
+
${canWrite ? html`<div class="s-css-actions">
|
|
264
|
+
<input ref=${file} type="file" class="sr-only" id="s-app-icon-file" accept="image/png,image/jpeg,image/webp" onChange=${choose} />
|
|
265
|
+
${picked ? html`
|
|
266
|
+
<button type="button" class="btn btn-primary" disabled=${busy} onClick=${use}><${Icon} name="check" size=${16} />${tn("appIconUse")}</button>
|
|
267
|
+
<button type="button" class="btn" disabled=${busy} onClick=${() => setPicked(null)}>${tn("appIconCancel")}</button>`
|
|
268
|
+
: html`<button type="button" class="btn" disabled=${busy} onClick=${() => file.current && file.current.click()}>${tn("appIconChoose")}</button>`}
|
|
269
|
+
${state.custom && !picked ? html`<button type="button" class="btn" disabled=${busy} onClick=${reset}>${tn("appIconReset")}</button>` : null}
|
|
270
|
+
</div>` : html`<p class="sub">${tn("appIconReadOnly")}</p>`}
|
|
271
|
+
<p class="sub">${tn("appIconWho")}</p>
|
|
272
|
+
<p class="sub">${tn("appIconIos")}</p>
|
|
273
|
+
</section>`;
|
|
274
|
+
}
|
|
275
|
+
|
|
130
276
|
const LIMIT_FIELDS = [["portrait", "max_width"], ["portrait", "max_height"], ["portrait", "max_kib"], ["banner", "max_width"], ["banner", "max_height"], ["banner", "max_kib"],
|
|
131
277
|
["instance_quota", "files"], ["instance_quota", "mib"]];
|
|
132
278
|
/**
|
|
@@ -222,6 +368,7 @@ function SidebarWidgetCard() {
|
|
|
222
368
|
const app = useStore(appStore);
|
|
223
369
|
const saved = sidebarWidgetRaw();
|
|
224
370
|
const [text, setText] = useState(() => (saved ? JSON.stringify(saved, null, 2) : ""));
|
|
371
|
+
const area = useRef(null);
|
|
225
372
|
const [error, setError] = useState("");
|
|
226
373
|
let parsed = null, problem = "";
|
|
227
374
|
if (text.trim()) {
|
|
@@ -234,17 +381,17 @@ function SidebarWidgetCard() {
|
|
|
234
381
|
setSidebarWidget(parsed);
|
|
235
382
|
toast(tn("widgetSaved"));
|
|
236
383
|
};
|
|
237
|
-
const remove = () => { setSidebarWidget(null); setText(""); setError(""); toast(tn("widgetRemoved")); };
|
|
384
|
+
const remove = () => { setSidebarWidget(null); recordEdit(area.current); setText(""); setError(""); toast(tn("widgetRemoved")); };
|
|
238
385
|
return html`<section class="card s-appearance s-synced s-widget"><${CardTitle} title=${tn("widgetTitle")} topic="widgets" />
|
|
239
386
|
<p class="sub">${tn("widgetLead")}</p>
|
|
240
387
|
<label class="field"><span class="sr-only">${tn("widgetTitle")}</span>
|
|
241
|
-
<textarea class="s-widget-text" rows="10" spellcheck=${false} autocapitalize="off" autocomplete="off" value=${text}
|
|
388
|
+
<textarea ref=${area} class="s-widget-text" rows="10" spellcheck=${false} autocapitalize="off" autocomplete="off" value=${text}
|
|
242
389
|
onInput=${(e) => { setText(e.target.value); setError(""); }}></textarea></label>
|
|
243
390
|
${error || problem ? html`<p class="err" role="alert">${error || problem}</p>` : null}
|
|
244
391
|
${preview ? html`<div class="s-widget-preview"><${AgentWidget} slot="sidebar" widget=${preview} binds=${fleetBinds(app.instances, app.exec, app.awaiting)} /></div>` : null}
|
|
245
392
|
<div class="s-widget-actions">
|
|
246
393
|
<button type="button" class="btn btn-primary" disabled=${!parsed || !!problem} onClick=${save}><${Icon} name="check" size=${16} />${tn("widgetSave")}</button>
|
|
247
|
-
${!text.trim() ? html`<button type="button" class="btn" onClick=${() => setText(JSON.stringify(SIDEBAR_WIDGET_EXAMPLE, null, 2))}>${tn("widgetExample")}</button>` : null}
|
|
394
|
+
${!text.trim() ? html`<button type="button" class="btn" onClick=${() => { recordEdit(area.current); setText(JSON.stringify(SIDEBAR_WIDGET_EXAMPLE, null, 2)); }}>${tn("widgetExample")}</button>` : null}
|
|
248
395
|
${saved ? html`<button type="button" class="btn" onClick=${remove}>${tn("widgetRemove")}</button>` : null}
|
|
249
396
|
</div>
|
|
250
397
|
</section>`;
|
|
@@ -253,6 +400,7 @@ function SidebarWidgetCard() {
|
|
|
253
400
|
function OwnerCssCard() {
|
|
254
401
|
const [state, setState] = useState(null);
|
|
255
402
|
const [text, setText] = useState("");
|
|
403
|
+
const area = useRef(null);
|
|
256
404
|
const [busy, setBusy] = useState(false);
|
|
257
405
|
const [error, setError] = useState("");
|
|
258
406
|
const trial = useStore(ownerCssTrial);
|
|
@@ -261,6 +409,8 @@ function OwnerCssCard() {
|
|
|
261
409
|
const r = await call("/ui/owner-css/state");
|
|
262
410
|
if (!r.ok || !r.body) { setState({ failed: true }); return; }
|
|
263
411
|
setState(r.body);
|
|
412
|
+
// #2342: a restored version replaces the editor's text as a step Ctrl+Z goes back from.
|
|
413
|
+
if (opts.undoable) recordEdit(area.current);
|
|
264
414
|
if (opts.text !== false) setText(r.body.text || "");
|
|
265
415
|
};
|
|
266
416
|
useEffect(() => { load(); }, []);
|
|
@@ -284,7 +434,7 @@ function OwnerCssCard() {
|
|
|
284
434
|
setBusy(false);
|
|
285
435
|
if (!r.ok) { setError(refusal(r.body)); return; }
|
|
286
436
|
setOwnerCssActive(r.body.active);
|
|
287
|
-
load();
|
|
437
|
+
load({ undoable: true });
|
|
288
438
|
};
|
|
289
439
|
const addAsset = async () => {
|
|
290
440
|
const f = file.current && file.current.files && file.current.files[0];
|
|
@@ -315,7 +465,7 @@ function OwnerCssCard() {
|
|
|
315
465
|
<p class="sub">${tn("cssLead")}</p>
|
|
316
466
|
<p class="s-css-status" role="status">${inForce}</p>
|
|
317
467
|
<label class="field"><span class="sr-only">${tn("cssTitle")}</span>
|
|
318
|
-
<textarea class="s-css-text" rows="14" spellcheck=${false} autocapitalize="off" autocomplete="off" value=${text} disabled=${busy}
|
|
468
|
+
<textarea ref=${area} class="s-css-text" rows="14" spellcheck=${false} autocapitalize="off" autocomplete="off" value=${text} disabled=${busy}
|
|
319
469
|
onInput=${(e) => setText(e.target.value)}></textarea></label>
|
|
320
470
|
${error ? html`<p class="err" role="alert">${error}</p>` : null}
|
|
321
471
|
<div class="s-css-actions">
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// provider key): each goes through confirmedWrite, so a change an admin must confirm waits for it (#1423).
|
|
5
5
|
import { html, useEffect, useMemo, useRef, useState } from "/assets/app-html.js";
|
|
6
6
|
import { t } from "/assets/app-i18n.js";
|
|
7
|
+
import { recordEdit } from "/assets/text-undo.js";
|
|
7
8
|
import { useLease } from "/assets/app-ctx.js";
|
|
8
9
|
import { Dialog } from "/assets/ui-dialog.js";
|
|
9
10
|
import { Icon } from "/assets/ui-icons.js";
|
|
@@ -16,11 +17,13 @@ import { startOperation } from "./settings-apply.js";
|
|
|
16
17
|
import "./settings-strings.js"; // its words, wherever a picker of this file is used (#2072: Fleet's create dialog too)
|
|
17
18
|
import { backendChoices, modelChoices, OTHER_MODEL, useBackendRows, useModelCatalog } from "./settings-catalog.js";
|
|
18
19
|
import {
|
|
19
|
-
ACCESS_MODES,
|
|
20
|
+
ACCESS_MODES, AGENT_MODES, CH_TYPES, HANG_TIMEOUT_DEFAULT_MINUTES, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS,
|
|
20
21
|
VISIBILITY_MODES, accessConfirmations, agentForm, agentPatch, backendOptions, changedFields, channelId, channelsOf,
|
|
21
|
-
chLabel, hasOwn, impactOf, nonNegative, nonNegativeInteger, positive, replyGuardSupported, sameValue, visibilityDefault,
|
|
22
|
+
chLabel, hasOwn, impactOf, newConnectionImpact, nonNegative, nonNegativeInteger, positive, replyGuardSupported, sameValue, visibilityDefault,
|
|
22
23
|
classicWebEchoEffective, worstImpact,
|
|
23
24
|
} from "./settings-model.js";
|
|
25
|
+
/** #2342: a field by id, for recordEdit before a pick or a reset writes it. */
|
|
26
|
+
const byId = (id) => (typeof document !== "undefined" ? document.getElementById(id) : null);
|
|
24
27
|
|
|
25
28
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
26
29
|
export const impactText = (kind) => tn(kind === "now" ? "impactNow" : kind === "fleet" ? "impactFleet" : "impactAgent");
|
|
@@ -103,7 +106,7 @@ export function ChipList({ items, onChange, placeholder, label }) {
|
|
|
103
106
|
|
|
104
107
|
const replyHint = (backend, mode, kiroUi) => (replyGuardSupported(backend, mode, kiroUi) ? tn("replyGuardSupported") : tn("replyGuardUnsupported", backend, mode));
|
|
105
108
|
|
|
106
|
-
/** The dialog every editor uses: Cancel / Stage change; the footer says what the
|
|
109
|
+
/** The dialog every editor uses: Cancel / Stage change; the footer says what the changes made so far will cost (#2346: only the fields that changed). */
|
|
107
110
|
function EditDialog({ title, subtitle, onClose, onStage, impacts, schema, children, ready = true, wide = true }) {
|
|
108
111
|
const kinds = [...new Set((impacts || []).map((f) => impactOf(schema, f)))];
|
|
109
112
|
return html`<${Dialog} title=${title} label=${subtitle ? `${title} — ${subtitle}` : title} onClose=${onClose} wide=${wide}
|
|
@@ -184,9 +187,9 @@ export function StatusEmojiEditor({ own, platform, channel, previewBody, onChang
|
|
|
184
187
|
<label for=${`se-${key}`}>${tn(`se_${key}`)}</label>
|
|
185
188
|
<input id=${`se-${key}`} type="text" autocomplete="off" value=${values[key] || ""} placeholder=${builtins[key] || ""} onInput=${(e) => set(key, e.target.value)} />
|
|
186
189
|
<button type="button" class="btn btn-sm" onClick=${() => setPicker(picker === key ? null : key)} aria-expanded=${picker === key ? "true" : "false"}>${tn("se_pick")}</button>
|
|
187
|
-
<button type="button" class="icon-btn" aria-label=${tn("se_useDefault")} title=${tn("se_useDefault")} onClick=${() => set(key, "")}><${Icon} name="close" size=${14} /></button>
|
|
190
|
+
<button type="button" class="icon-btn" aria-label=${tn("se_useDefault")} title=${tn("se_useDefault")} onClick=${() => { recordEdit(byId(`se-${key}`)); set(key, ""); }}><${Icon} name="close" size=${14} /></button>
|
|
188
191
|
${picker === key ? html`<${EmojiPicker} cat=${cat} keyName=${key} platform=${platform} channel=${channel}
|
|
189
|
-
onPick=${(v) => { set(key, v); setPicker(null); }} onClose=${() => setPicker(null)} />` : null}
|
|
192
|
+
onPick=${(v) => { recordEdit(byId(`se-${key}`)); set(key, v); setPicker(null); }} onClose=${() => setPicker(null)} />` : null}
|
|
190
193
|
</div>`)}</div>
|
|
191
194
|
<p class="note">${tn("se_hint")}</p>
|
|
192
195
|
</div>`;
|
|
@@ -241,16 +244,17 @@ export function AgentDialog({ name, inst, ctx, onClose }) {
|
|
|
241
244
|
};
|
|
242
245
|
const failoverWarn = !f.inherit.model_failover && !f.model_failover.trim() ? "failoverEmpty" : "";
|
|
243
246
|
const ready = !errors.auto && !errors.hang;
|
|
247
|
+
const pending = () => agentPatch({ ...f, status_emojis: emojis.value }, inst, defaults, visibilityBaseline, emojis.baseline);
|
|
244
248
|
const stage = () => {
|
|
245
249
|
if (!ready) return;
|
|
246
|
-
const patch =
|
|
250
|
+
const patch = pending();
|
|
247
251
|
if (Object.keys(patch).length) ctx.stageAgent(name, inst, patch);
|
|
248
252
|
onClose();
|
|
249
253
|
};
|
|
250
254
|
// What an override shows while it inherits: the default.
|
|
251
255
|
const shown = (k, own, dflt) => (f.inherit[k] ? dflt : own);
|
|
252
256
|
const L = (text, field, id) => html`<${Label} text=${text} schema=${schema} field=${field} htmlFor=${id} />`;
|
|
253
|
-
return html`<${EditDialog} title=${ctx.shortName(name, inst)} subtitle=${`ID: ${name}`} onClose=${onClose} onStage=${stage} ready=${ready} impacts=${
|
|
257
|
+
return html`<${EditDialog} title=${ctx.shortName(name, inst)} subtitle=${`ID: ${name}`} onClose=${onClose} onStage=${stage} ready=${ready} impacts=${Object.keys(pending()).map((k) => `instance.${k}`)} schema=${schema}>
|
|
254
258
|
<div class="grid2">
|
|
255
259
|
<div class="field">${L(tn("displayName"), "instance.display_name", "ag-dn")}
|
|
256
260
|
<${Override} inherit=${f.inherit.display_name} onInherit=${setInherit("display_name")}>
|
|
@@ -406,8 +410,8 @@ export function BotDialog({ id, ctx, onClose }) {
|
|
|
406
410
|
setToken(""); setTokenId(null); setReplacing(false);
|
|
407
411
|
setTokenNote({ text: tn("tokenQueued") });
|
|
408
412
|
};
|
|
409
|
-
|
|
410
|
-
|
|
413
|
+
/** The connections with this dialog's edits, and which parts changed. */
|
|
414
|
+
const pending = () => {
|
|
411
415
|
const original = structuredClone(chs);
|
|
412
416
|
const next = structuredClone(original);
|
|
413
417
|
next[index] = { ...next[index], access: { ...(next[index].access || {}), mode, allowed_users: [...users] } };
|
|
@@ -418,6 +422,16 @@ export function BotDialog({ id, ctx, onClose }) {
|
|
|
418
422
|
if (emojis.value) options.status_emojis = emojis.value; else delete options.status_emojis;
|
|
419
423
|
next[index] = { ...next[index], options };
|
|
420
424
|
}
|
|
425
|
+
return { next, accessChanged, emojisChanged };
|
|
426
|
+
};
|
|
427
|
+
const pendingImpacts = () => {
|
|
428
|
+
if (!ch) return [];
|
|
429
|
+
const p = pending();
|
|
430
|
+
return [...(p.accessChanged ? ["fleet.channels"] : []), ...(p.emojisChanged ? ["fleet.channel.options.status_emojis"] : [])];
|
|
431
|
+
};
|
|
432
|
+
const stage = () => {
|
|
433
|
+
if (!ch) return;
|
|
434
|
+
const { next, accessChanged, emojisChanged } = pending();
|
|
421
435
|
if (accessChanged || emojisChanged) {
|
|
422
436
|
ctx.stageChannels(`connections:${ch.id || chLabel(index)}`, next, {
|
|
423
437
|
label: tn(accessChanged && emojisChanged ? "updateAccessEmojis" : accessChanged ? "updateAccess" : "updateEmojis", ch.id || chLabel(index)),
|
|
@@ -445,7 +459,7 @@ export function BotDialog({ id, ctx, onClose }) {
|
|
|
445
459
|
};
|
|
446
460
|
const L = (text, field, htmlFor) => html`<${Label} text=${text} schema=${schema} field=${field} htmlFor=${htmlFor} />`;
|
|
447
461
|
if (!ch) return null;
|
|
448
|
-
return html`<${EditDialog} title=${type === "telegram" ? "Telegram" : "Discord"} subtitle=${id} onClose=${onClose} onStage=${stage} impacts=${
|
|
462
|
+
return html`<${EditDialog} title=${type === "telegram" ? "Telegram" : "Discord"} subtitle=${id} onClose=${onClose} onStage=${stage} impacts=${pendingImpacts()} schema=${schema}>
|
|
449
463
|
<div class="grid2">
|
|
450
464
|
<div class="field">${L(tn("accessMode"), "fleet.channel.access.mode", "bot-mode")}<${Select} id="bot-mode" value=${mode} onChange=${setMode} options=${ACCESS_MODES} />
|
|
451
465
|
<${Feedback} warning=${accessWarn} /></div>
|
|
@@ -503,6 +517,7 @@ export function NewBotDialog({ ctx, onClose }) {
|
|
|
503
517
|
// The form's revision (#1529 review): any change to the token, platform, id or group moves it; a Verify or a plan
|
|
504
518
|
// that began before is dropped when it lands — never restoring an old token, naming it, or planning for it.
|
|
505
519
|
const rev = useRef(0);
|
|
520
|
+
// The fields keep what was typed (#2342: trimming the box would rewrite it, losing its undo); trimmed here.
|
|
506
521
|
const body = (x) => ({ platform: x.type, connection_only: true, admin_user_id: x.admin.trim(), ...(x.id.trim() ? { channel_id: x.id.trim() } : {}),
|
|
507
522
|
...(x.group.trim() ? (x.type === "discord" ? { guild_id: x.group.trim() } : { group_id: x.group.trim() }) : {}),
|
|
508
523
|
...(x.type === "discord" && x.general.trim() ? { general_channel_id: x.general.trim() } : {}) });
|
|
@@ -538,7 +553,7 @@ export function NewBotDialog({ ctx, onClose }) {
|
|
|
538
553
|
setBusy(true);
|
|
539
554
|
const p = await planFor(x);
|
|
540
555
|
if (!p) { if (lease.current()) setBusy(false); return; }
|
|
541
|
-
const handed = startOperation([{ label: tn("addBotLabel", p.channel_id), impact:
|
|
556
|
+
const handed = startOperation([{ label: tn("addBotLabel", p.channel_id), impact: newConnectionImpact(ctx.schema),
|
|
542
557
|
request: { method: "POST", url: "/api/settings/quickstart/commit",
|
|
543
558
|
body: { ...body(x), channel_id: p.channel_id, token_env: p.token_env, token_env_generated: true, token: x.token }, sensitive: true } }]);
|
|
544
559
|
if (!handed) { setErr(tn("applyBusyLocal")); setBusy(false); return; }
|
|
@@ -560,9 +575,9 @@ export function NewBotDialog({ ctx, onClose }) {
|
|
|
560
575
|
invite=${f.identity && f.identity.invite} portal=${f.identity && f.identity.portal} guilds=${f.guilds} onGuilds=${(g) => setF((x) => ({ ...x, guilds: g }))}
|
|
561
576
|
guild=${f.group} onGuild=${set("group")} channel=${f.general} onChannel=${set("general")} />`
|
|
562
577
|
: html`<div class="field"><label for="nb-group">${tn("groupIdField")}</label>
|
|
563
|
-
<input id="nb-group" type="text" value=${f.group} onInput=${(e) => set("group")(e.target.value
|
|
578
|
+
<input id="nb-group" type="text" value=${f.group} onInput=${(e) => set("group")(e.target.value)} /></div>`}
|
|
564
579
|
<div class="field"><label for="nb-user">${tn("wizardAdminUser")}</label>
|
|
565
|
-
<input id="nb-user" type="text" required=${true} inputmode="numeric" pattern=${adminUserIdPattern(f.type)} value=${f.admin} onInput=${(e) => set("admin")(e.target.value
|
|
580
|
+
<input id="nb-user" type="text" required=${true} inputmode="numeric" pattern=${adminUserIdPattern(f.type)} value=${f.admin} onInput=${(e) => set("admin")(e.target.value)} /></div>
|
|
566
581
|
<${Drawer} title=${tn("advancedSection")}>
|
|
567
582
|
<div class="field"><label for="nb-id">${tn("connectionIdOptional")}</label><input id="nb-id" type="text" placeholder=${plan ? plan.channel_id : f.type}
|
|
568
583
|
value=${f.id} onInput=${(e) => set("id")(e.target.value)} onBlur=${() => { if (f.identity && f.identity.valid) planFor(f); }} /></div>
|
|
@@ -597,9 +612,7 @@ export function ClassicDialog({ room, ctx, onClose }) {
|
|
|
597
612
|
const runtime = ctx.classicRuntime(c.instanceName);
|
|
598
613
|
const classicMode = fd.agent_mode ?? "mcp";
|
|
599
614
|
const lease = useLease("classic-dialog");
|
|
600
|
-
const
|
|
601
|
-
if (!ready) return;
|
|
602
|
-
const patch = changedFields({
|
|
615
|
+
const pending = () => changedFields({
|
|
603
616
|
backend: f.backend, model: f.model.trim() || null,
|
|
604
617
|
auto_pause_after: f.inherit.auto_pause_after ? null : Number(f.auto_pause_after),
|
|
605
618
|
tool_progress: f.inherit.tool_progress ? null : f.tool_progress,
|
|
@@ -612,13 +625,16 @@ export function ClassicDialog({ room, ctx, onClose }) {
|
|
|
612
625
|
reply_completion_guard: hasOwn(c, "reply_completion_guard") ? c.reply_completion_guard : null,
|
|
613
626
|
collab: !!c.collab, context_lines: c.context_lines ?? cd.context_lines ?? 5, web_echo: classicWebEchoEffective(c, fleet),
|
|
614
627
|
});
|
|
628
|
+
const stage = async () => {
|
|
629
|
+
if (!ready) return;
|
|
630
|
+
const patch = pending();
|
|
615
631
|
if (!Object.keys(patch).length) { onClose(); return; }
|
|
616
632
|
if (!lease.current()) return;
|
|
617
633
|
ctx.stageClassic(c, patch);
|
|
618
634
|
onClose();
|
|
619
635
|
};
|
|
620
636
|
const L = (text, field, id) => html`<${Label} text=${text} schema=${schema} field=${field} htmlFor=${id} />`;
|
|
621
|
-
return html`<${EditDialog} title=${c.name || c.instanceName} subtitle=${`ID: ${c.instanceName} · ${tn(`exec_${runtime.execution}`)}`} onClose=${onClose} onStage=${stage} ready=${ready} impacts=${
|
|
637
|
+
return html`<${EditDialog} title=${c.name || c.instanceName} subtitle=${`ID: ${c.instanceName} · ${tn(`exec_${runtime.execution}`)}`} onClose=${onClose} onStage=${stage} ready=${ready} impacts=${Object.keys(pending()).map((k) => `classic.${k}`)} schema=${schema}>
|
|
622
638
|
<div class="grid2">
|
|
623
639
|
<div class="field">${L(tn("backend"), "classic.backend", "cl-be")}<${BackendSelect} id="cl-be" value=${f.backend} onChange=${set("backend")} /></div>
|
|
624
640
|
<div class="field">${L(tn("model"), "classic.model", "cl-model")}<${ModelSelect} id="cl-model" value=${f.model} onChange=${set("model")}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// B, nor after A again (an older A), nor once the page is gone.
|
|
10
10
|
import { html, useEffect, useRef, useState } from "/assets/app-html.js";
|
|
11
11
|
import { t } from "/assets/app-i18n.js";
|
|
12
|
+
import { useTypedText } from "/assets/text-undo.js";
|
|
12
13
|
import { api } from "./settings-confirm.js";
|
|
13
14
|
import { Select } from "./settings-dialogs.js";
|
|
14
15
|
|
|
@@ -23,6 +24,9 @@ const probe = (body) => api("/api/settings/quickstart/probe", { method: "POST",
|
|
|
23
24
|
*/
|
|
24
25
|
export function DiscordServerPicker({ idPrefix, token, bot, invite, portal, guilds, onGuilds, guild, onGuild, channel, onChannel }) {
|
|
25
26
|
const [polling, setPolling] = useState(null); // { timedOut } while waiting for the bot to join
|
|
27
|
+
// #2342: the typed IDs keep what was typed (a space, the undo history); the picks get them trimmed.
|
|
28
|
+
const [guildText, onGuildText] = useTypedText(guild);
|
|
29
|
+
const [channelText, onChannelText] = useTypedText(channel);
|
|
26
30
|
const [channels, setChannels] = useState(null); // null: not asked; { list } | { error }
|
|
27
31
|
const live = useRef(true);
|
|
28
32
|
const now = useRef({ token, guild, channel });
|
|
@@ -120,11 +124,11 @@ export function DiscordServerPicker({ idPrefix, token, bot, invite, portal, guil
|
|
|
120
124
|
<div class="field"><label for=${`${idPrefix}-guild`}>${tn("guildIdField")}</label>
|
|
121
125
|
${guilds && guilds.length
|
|
122
126
|
? html`<${Select} id=${`${idPrefix}-guild`} value=${guild} onChange=${(v) => pickGuild(v, true)} options=${["", ...guilds.map((g) => ({ value: g.id, label: `${g.name} (${g.id})` }))]} />`
|
|
123
|
-
: html`<input id=${`${idPrefix}-guild`} type="text" value=${
|
|
127
|
+
: html`<input id=${`${idPrefix}-guild`} type="text" value=${guildText} placeholder=${tn("discordNoServerYet")} onInput=${(e) => pickGuild(onGuildText(e.target.value), true)} />`}</div>
|
|
124
128
|
<div class="field"><label for=${`${idPrefix}-gen`}>${tn("wizardGeneralChannel")}</label>
|
|
125
129
|
${channels && channels.list && channels.list.length
|
|
126
130
|
? html`<${Select} id=${`${idPrefix}-gen`} value=${channel} onChange=${pickChannel} options=${["", ...channels.list.map((c) => ({ value: c.id, label: `#${c.name}` }))]} />`
|
|
127
|
-
: html`<input id=${`${idPrefix}-gen`} type="text" value=${
|
|
131
|
+
: html`<input id=${`${idPrefix}-gen`} type="text" value=${channelText} onInput=${(e) => pickChannel(onChannelText(e.target.value))} />`}
|
|
128
132
|
${channels && channels.error ? html`<p class="feedback warning">${channels.error}</p>` : null}</div>
|
|
129
133
|
</div>`;
|
|
130
134
|
}
|
|
@@ -8,6 +8,7 @@ import { t } from "/assets/app-i18n.js";
|
|
|
8
8
|
import { toast } from "/assets/ui-toast.js";
|
|
9
9
|
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
10
10
|
import { api } from "./settings-confirm.js";
|
|
11
|
+
import { DEFAULT_SCHEMA, impactOf } from "./settings-model.js";
|
|
11
12
|
import "./settings-strings.js";
|
|
12
13
|
|
|
13
14
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
@@ -59,7 +60,7 @@ export function GuestsCard({ ctx, staged, disabled = false }) {
|
|
|
59
60
|
const setList = (key, k, list) => setF((x) => { const m = { ...x[key] }; if (list === undefined) delete m[k]; else m[k] = list; return { ...x, [key]: m }; });
|
|
60
61
|
const review = () => {
|
|
61
62
|
const body = guestsBody(original, f);
|
|
62
|
-
if (body) ctx.stage("guests", { label: tn("guestsTitle"), impact: "
|
|
63
|
+
if (body) ctx.stage("guests", { label: tn("guestsTitle"), impact: impactOf(ctx.schema || DEFAULT_SCHEMA, "web.guests"), request: { method: "PUT", url: "/api/settings/fleet/web", body: { guests: body } } });
|
|
63
64
|
else if (staged.has("guests")) ctx.unstage("guests");
|
|
64
65
|
setMsg(tn("readyApply"));
|
|
65
66
|
};
|
|
@@ -13,6 +13,7 @@ export const LEARN_ANCHORS = {
|
|
|
13
13
|
colors: ["identity-colours", "識別色"], css: ["custom-css-settings--web-appearance", "自訂-css設定--網頁外觀"], themes: ["agents-themes", "agent-的主題"],
|
|
14
14
|
cards: ["identity-cards", "身分卡"], limits: ["card-upload-limits", "身分卡上傳限制"], widgets: ["widgets", "小工具"], mods: ["mods", "mod"],
|
|
15
15
|
effects: ["effects", "效果"], restore: ["restore-defaults", "還原預設值"],
|
|
16
|
+
"app-icon": ["app-icon", "app-圖示"],
|
|
16
17
|
};
|
|
17
18
|
export function learnHref(topic, language = lang()) {
|
|
18
19
|
const zh = language === "zh-TW";
|
|
@@ -56,6 +56,8 @@ export const chLabel = (i) => (i === 0 ? "primary" : "persona");
|
|
|
56
56
|
// ── Impacts: the server's (/api/settings/schema, derived from HOT_INSTANCE_CONFIG_KEYS); this page never decides one ──
|
|
57
57
|
export const DEFAULT_SCHEMA = { impacts: {}, order: ["now", "instance", "fleet"] };
|
|
58
58
|
export const impactOf = (schema, field) => (schema.impacts && schema.impacts[field]) || "instance";
|
|
59
|
+
/** #2346: a new connection starts at once while another one runs (the schema says so); the first one needs AgEnD restarted. */
|
|
60
|
+
export const newConnectionImpact = (schema) => (schema && schema.connections_start_live === true ? "now" : impactOf(schema || DEFAULT_SCHEMA, "fleet.channels"));
|
|
59
61
|
/** A batch costs as much as its worst member, in the order the server ships. */
|
|
60
62
|
export function batchImpact(schema, keys, impactFor) {
|
|
61
63
|
const order = schema.order;
|
|
@@ -197,17 +199,6 @@ export function agentForm(inst, defaults) {
|
|
|
197
199
|
};
|
|
198
200
|
}
|
|
199
201
|
|
|
200
|
-
/** Every field the agent dialog shows, for its footer's impact summary. */
|
|
201
|
-
export const AGENT_FIELDS = [
|
|
202
|
-
"instance.display_name", "instance.description", "instance.backend", "instance.model", "instance.working_directory",
|
|
203
|
-
"instance.auto_pause_after", "instance.topic_id", "instance.hang_detector", "instance.agent_mode", "instance.tool_set",
|
|
204
|
-
"instance.tool_progress", "instance.cross_instance_visibility", "instance.reply_completion_guard", "instance.log_level",
|
|
205
|
-
"instance.lightweight", "instance.model_failover", "instance.systemPrompt", "instance.tags", "instance.general_topic",
|
|
206
|
-
"instance.status_emojis",
|
|
207
|
-
];
|
|
208
|
-
export const CLASSIC_FIELDS = ["classic.backend", "classic.model", "classic.auto_pause_after", "classic.tool_progress",
|
|
209
|
-
"classic.reply_completion_guard", "classic.collab", "classic.context_lines", "classic.web_echo"];
|
|
210
|
-
export const BOT_FIELDS = ["fleet.channel.access.mode", "fleet.channel.access.allowed_users", "fleet.channels", "fleet.channel.options.status_emojis"];
|
|
211
202
|
|
|
212
203
|
/** Validation messages (keys of the settings namespace), or "" when the value is fine. */
|
|
213
204
|
export const nonNegative = (v) => (!Number.isFinite(Number(v)) || String(v).trim() === "" || Number(v) < 0 ? "mustNonNegative" : "");
|
package/dist/ui/settings-mods.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// parts the person ticks are applied, all or nothing. Custom CSS from a file is never ticked by default. Installed
|
|
5
5
|
// mods can be downloaded again or uninstalled (what they applied stays: Reset and the CSS controls work on it).
|
|
6
6
|
// Export builds a file from what is in force now.
|
|
7
|
+
import { canvasWorkerEnabled, useCanvasWorker } from "/assets/feature-flags.js";
|
|
7
8
|
import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
|
|
8
9
|
import { t } from "/assets/app-i18n.js";
|
|
9
10
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
@@ -45,7 +46,7 @@ export function defaultChoice(plan) {
|
|
|
45
46
|
for (const it of plan.items) {
|
|
46
47
|
if (it.part === "theme" || it.part === "identity" || it.part === "ambient") c[it.part] = true;
|
|
47
48
|
else if (it.part === "widget") c.widgets[it.slot] = true;
|
|
48
|
-
else if (it.part === "effect") c.effects = true;
|
|
49
|
+
else if (it.part === "effect" && canvasWorkerEnabled()) c.effects = true;
|
|
49
50
|
// ownerCss stays false: "read it before turning it on" (r4 §4).
|
|
50
51
|
}
|
|
51
52
|
return c;
|
|
@@ -80,7 +81,7 @@ export function ThemeDetail({ theme, pictures, effects }) {
|
|
|
80
81
|
${th.background ? pic(th.background.image, tn("modBackground")) : null}
|
|
81
82
|
${th.background ? row(tn("modBackgroundFit"), `${th.background.fit} · ${Math.round(th.background.opacity * 100)}%`) : null}
|
|
82
83
|
${th.avatar ? pic(th.avatar, tn("modAvatar")) : null}
|
|
83
|
-
${(effects || []).map((e, i) => html`<div key=${i}>${row(tn("modPlacesEffect"), html`<span><code>${e.effect}</code>${e.id ? html` <span class="muted">${e.id.slice(0, 8)}</span>` : null}${Object.keys(e.params || {}).length ? html` · <code>${JSON.stringify(e.params)}</code>` : null}</span>`)}</div>`)}
|
|
84
|
+
${(canvasWorkerEnabled() ? effects || [] : []).map((e, i) => html`<div key=${i}>${row(tn("modPlacesEffect"), html`<span><code>${e.effect}</code>${e.id ? html` <span class="muted">${e.id.slice(0, 8)}</span>` : null}${Object.keys(e.params || {}).length ? html` · <code>${JSON.stringify(e.params)}</code>` : null}</span>`)}</div>`)}
|
|
84
85
|
<details class="s-mod-raw"><summary>${tn("modAllFields")}</summary><pre class="s-mod-css-text">${JSON.stringify(th, null, 2)}</pre></details>
|
|
85
86
|
</div>`;
|
|
86
87
|
}
|
|
@@ -114,6 +115,7 @@ function PlanItem({ it, choice, setChoice, binds, fleet }) {
|
|
|
114
115
|
<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
116
|
}
|
|
116
117
|
if (it.part === "effect") {
|
|
118
|
+
if (!canvasWorkerEnabled()) return null;
|
|
117
119
|
return html`<li class="s-mod-item">${tick(tn("modPartEffect", it.title), choice.effects, (v) => setChoice({ ...choice, effects: v }))}
|
|
118
120
|
<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>`;
|
|
119
121
|
}
|
|
@@ -137,7 +139,7 @@ function Plan({ staged, onDone }) {
|
|
|
137
139
|
const names = (app.instances || []).map((i) => i.name);
|
|
138
140
|
const install = async () => {
|
|
139
141
|
setBusy(true); setError("");
|
|
140
|
-
const r = await postJson("/ui/mods/commit", { stage: staged.stage, plan_hash: staged.plan_hash, include: choice, ...(toInstance ? { target } : {}) });
|
|
142
|
+
const r = await postJson("/ui/mods/commit", { stage: staged.stage, plan_hash: staged.plan_hash, include: { ...choice, effects: canvasWorkerEnabled() && choice.effects }, ...(toInstance ? { target } : {}) });
|
|
141
143
|
setBusy(false);
|
|
142
144
|
// The stage this plan shows is gone or no longer this plan (replaced on another device, expired): read again —
|
|
143
145
|
// the page then shows whatever is staged now, with its own, fresh choices (keyed by stage).
|
|
@@ -205,6 +207,7 @@ function ExportForm() {
|
|
|
205
207
|
}
|
|
206
208
|
|
|
207
209
|
export function ModsCard() {
|
|
210
|
+
useCanvasWorker();
|
|
208
211
|
const [state, setState] = useState(null);
|
|
209
212
|
const [busy, setBusy] = useState(false);
|
|
210
213
|
const [error, setError] = useState("");
|