@songsid/agend 2.3.0-beta.1 → 2.3.0-beta.11
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 +5 -2
- package/dist/agent-cli.js +43 -7
- 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.d.ts +14 -6
- package/dist/agent-file-read.js +207 -19
- package/dist/agent-file-read.js.map +1 -1
- package/dist/agent-whats-new.d.ts +11 -0
- package/dist/agent-whats-new.js +29 -15
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +30 -1
- package/dist/auth-api.js +45 -13
- 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 +120 -0
- package/dist/authority/web-authority.js +157 -0
- package/dist/authority/web-authority.js.map +1 -0
- package/dist/backend/claude-code.d.ts +2 -2
- package/dist/backend/claude-code.js +10 -5
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/types.d.ts +14 -0
- package/dist/backend/types.js +25 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-availability.d.ts +120 -0
- package/dist/backend-availability.js +396 -0
- package/dist/backend-availability.js.map +1 -0
- package/dist/backend-command.d.ts +21 -3
- package/dist/backend-command.js +23 -4
- package/dist/backend-command.js.map +1 -1
- package/dist/bundled-skills.d.ts +14 -0
- package/dist/bundled-skills.js +29 -0
- package/dist/bundled-skills.js.map +1 -0
- package/dist/cache-analysis.d.ts +47 -1
- package/dist/cache-analysis.js +68 -8
- package/dist/cache-analysis.js.map +1 -1
- package/dist/cache-ledger.d.ts +16 -3
- package/dist/cache-ledger.js +33 -13
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-report-tool.d.ts +17 -0
- package/dist/cache-report-tool.js +40 -0
- package/dist/cache-report-tool.js.map +1 -0
- package/dist/cache-service.d.ts +70 -3
- package/dist/cache-service.js +178 -17
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +7 -0
- package/dist/channel/adapters/discord.js +35 -0
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-tools.js +22 -10
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/types.d.ts +6 -0
- package/dist/classic-channel-manager.d.ts +10 -5
- package/dist/classic-channel-manager.js +12 -9
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +130 -31
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +103 -4
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +7 -0
- package/dist/daemon.js +63 -16
- package/dist/daemon.js.map +1 -1
- package/dist/decision-list.d.ts +25 -0
- package/dist/decision-list.js +30 -0
- package/dist/decision-list.js.map +1 -0
- package/dist/delivery-outbox.d.ts +10 -0
- package/dist/delivery-outbox.js +12 -1
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/fleet-context.d.ts +10 -0
- package/dist/fleet-manager.d.ts +417 -11
- package/dist/fleet-manager.js +1387 -109
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-traffic.d.ts +74 -0
- package/dist/fleet-traffic.js +196 -0
- package/dist/fleet-traffic.js.map +1 -0
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +20 -5
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +3 -2
- package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +257 -23
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +478 -0
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
- package/dist/human-delivery-receipts.d.ts +24 -1
- package/dist/human-delivery-receipts.js +64 -11
- package/dist/human-delivery-receipts.js.map +1 -1
- package/dist/instance-config-impact.js +3 -1
- package/dist/instance-config-impact.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 +12 -1
- package/dist/instructions.js +13 -2
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +100 -8
- package/dist/locale.js.map +1 -1
- package/dist/ls-attach-resolve.d.ts +95 -0
- package/dist/ls-attach-resolve.js +174 -0
- package/dist/ls-attach-resolve.js.map +1 -0
- package/dist/memory-hogs.d.ts +27 -0
- package/dist/memory-hogs.js +124 -0
- package/dist/memory-hogs.js.map +1 -0
- package/dist/outbound-handlers.d.ts +10 -0
- package/dist/outbound-handlers.js +122 -2
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +164 -4
- package/dist/outbound-schemas.js +99 -3
- 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/public-web-link.d.ts +9 -0
- package/dist/public-web-link.js +8 -0
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.d.ts +10 -1
- package/dist/quickstart-api.js +12 -4
- package/dist/quickstart-api.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +4 -0
- package/dist/reply-buttons-controller.js +2 -2
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/restart-progress.d.ts +65 -1
- package/dist/restart-progress.js +236 -10
- package/dist/restart-progress.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/scheduler/scheduler.d.ts +12 -0
- package/dist/scheduler/scheduler.js +83 -10
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/service-installer.d.ts +2 -1
- package/dist/service-installer.js +18 -3
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.d.ts +19 -0
- package/dist/settings-api.js +137 -2
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +104 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +17 -1
- package/dist/settings-effect.js.map +1 -1
- package/dist/tmux-manager.d.ts +5 -0
- package/dist/tmux-manager.js +16 -0
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tool-permissions.d.ts +4 -6
- package/dist/tool-permissions.js +53 -5
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +7 -1
- package/dist/topic-commands.js +18 -4
- package/dist/topic-commands.js.map +1 -1
- package/dist/types.d.ts +48 -1
- package/dist/ui/app-effects.js +77 -31
- package/dist/ui/chat-ambient.js +307 -0
- package/dist/ui/chat-effect-posters.js +8 -8
- package/dist/ui/chat-effects.js +101 -13
- package/dist/ui/chat-render.js +21 -3
- package/dist/ui/chat-strings.js +59 -6
- package/dist/ui/chat-thread.js +159 -21
- package/dist/ui/effect-trial-controller.js +13 -8
- package/dist/ui/effect-trial-panel.js +19 -16
- package/dist/ui/fleet-cache.js +23 -1
- package/dist/ui/fleet-canvas-layout.js +111 -0
- package/dist/ui/fleet-canvas.js +525 -0
- package/dist/ui/fleet-org.js +27 -12
- package/dist/ui/fleet-traffic.js +343 -0
- package/dist/ui/fleet-usage.js +401 -0
- package/dist/ui/panel-chat.js +154 -49
- package/dist/ui/panel-details.js +2 -1
- package/dist/ui/panel-fleet.js +43 -17
- package/dist/ui/panel-settings.js +26 -8
- package/dist/ui/preview.js +3 -2
- package/dist/ui/settings-appearance.js +42 -18
- package/dist/ui/settings-catalog.js +92 -0
- package/dist/ui/settings-dialogs.js +40 -12
- package/dist/ui/settings-effects.js +4 -3
- package/dist/ui/settings-guests.js +117 -0
- package/dist/ui/settings-learn.js +25 -0
- package/dist/ui/settings-model.js +15 -6
- package/dist/ui/settings-mods.js +23 -9
- package/dist/ui/settings-owner-effects.js +9 -9
- package/dist/ui/settings-strings.js +120 -42
- package/dist/ui/settings-web-authority.js +91 -0
- package/dist/ui/shared/agend-auth.js +1 -1
- package/dist/ui/shared/app-i18n.js +4 -2
- package/dist/ui/shared/app-prefs.js +5 -4
- package/dist/ui/shared/app-route.js +19 -1
- package/dist/ui/shared/app-session.js +2 -0
- package/dist/ui/shared/app-shell.js +92 -27
- package/dist/ui/shared/app-store.js +26 -1
- package/dist/ui/shared/app-stream.js +3 -1
- package/dist/ui/shared/app-themes.js +24 -6
- package/dist/ui/shared/app.css +365 -17
- package/dist/ui/shared/app.js +21 -14
- package/dist/ui/shared/card-strings.js +4 -0
- package/dist/ui/shared/effect-app-runtime.js +68 -19
- package/dist/ui/shared/effect-chat-runtime.js +16 -3
- package/dist/ui/shared/effect-frame.js +26 -7
- package/dist/ui/shared/effect-geometry.js +24 -9
- package/dist/ui/shared/effect-policy.js +21 -7
- package/dist/ui/shared/effect-profiler.js +31 -9
- package/dist/ui/shared/effect-registry.js +87 -48
- package/dist/ui/shared/effect-stock-snapshot.js +9 -0
- package/dist/ui/shared/effect-stock-trust.js +62 -10
- package/dist/ui/shared/effect-trial-shadow.js +62 -0
- package/dist/ui/shared/effect-trial-source.js +1 -1
- package/dist/ui/shared/effect-trial-stage.js +54 -31
- package/dist/ui/shared/effect-trial.css +40 -0
- package/dist/ui/shared/fx-motion.js +67 -0
- package/dist/ui/shared/identity-card.js +75 -4
- package/dist/ui/shared/identity-color.js +3 -3
- package/dist/ui/shared/instance-nav.js +194 -28
- package/dist/ui/shared/panel-view.js +24 -4
- package/dist/ui/shared/tokens.css +9 -3
- package/dist/ui/shared/ui-icons.js +1 -0
- package/dist/ui/shared/usage-dialog.js +22 -2
- package/dist/ui/shared/view-strings.js +68 -2
- package/dist/ui/shared/web-authorize.js +151 -0
- package/dist/ui/signin.html +1 -1
- package/dist/ui/web-terminal/terminal.css +4 -4
- package/dist/usage/providers.d.ts +28 -0
- package/dist/usage/providers.js +126 -36
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage-report.d.ts +158 -0
- package/dist/usage-report.js +226 -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/view-api.d.ts +39 -0
- package/dist/view-api.js +65 -4
- package/dist/view-api.js.map +1 -1
- package/dist/web-ambient.d.ts +72 -0
- package/dist/web-ambient.js +161 -0
- package/dist/web-ambient.js.map +1 -0
- package/dist/web-api.d.ts +69 -5
- package/dist/web-api.js +362 -45
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-policy.d.ts +4 -6
- package/dist/web-appearance-policy.js +5 -12
- package/dist/web-appearance-policy.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +3 -0
- package/dist/web-appearance-tools.js +22 -3
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.d.ts +27 -2
- package/dist/web-appearance-view.js +129 -14
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +17 -3
- package/dist/web-appearance.js +82 -7
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-asset-parts.d.ts +5 -10
- 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-card.d.ts +14 -0
- package/dist/web-card.js +40 -6
- package/dist/web-card.js.map +1 -1
- package/dist/web-channel-echo.d.ts +7 -1
- package/dist/web-channel-echo.js +6 -2
- package/dist/web-channel-echo.js.map +1 -1
- package/dist/web-chat-history.d.ts +50 -0
- package/dist/web-chat-history.js +87 -5
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +1 -0
- 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 +28 -5
- package/dist/web-effect-service.js.map +1 -1
- package/dist/web-effect-tools.d.ts +1 -0
- package/dist/web-effect-tools.js +8 -5
- package/dist/web-effect-tools.js.map +1 -1
- package/dist/web-effect-trials.d.ts +4 -4
- package/dist/web-effect-trials.js +6 -6
- package/dist/web-effect-trials.js.map +1 -1
- package/dist/web-effect-upload.d.ts +2 -4
- package/dist/web-effect-upload.js +3 -8
- package/dist/web-effect-upload.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-effect.d.ts +14 -0
- package/dist/web-effect.js +21 -0
- package/dist/web-effect.js.map +1 -1
- package/dist/web-guest-access.d.ts +125 -0
- package/dist/web-guest-access.js +234 -0
- package/dist/web-guest-access.js.map +1 -0
- package/dist/web-guest-rooms.d.ts +82 -0
- package/dist/web-guest-rooms.js +135 -0
- package/dist/web-guest-rooms.js.map +1 -0
- package/dist/web-identity-color.d.ts +1 -1
- package/dist/web-identity-color.js +2 -2
- package/dist/web-identity-color.js.map +1 -1
- package/dist/web-identity-palette.d.ts +1 -1
- package/dist/web-identity-palette.js +2 -2
- package/dist/web-identity-palette.js.map +1 -1
- package/dist/web-image-meta.d.ts +13 -0
- package/dist/web-image-meta.js +62 -0
- package/dist/web-image-meta.js.map +1 -1
- package/dist/web-login.d.ts +47 -0
- package/dist/web-login.js +115 -0
- package/dist/web-login.js.map +1 -1
- package/dist/web-mod-agent.d.ts +1 -1
- package/dist/web-mod-agent.js +12 -8
- package/dist/web-mod-agent.js.map +1 -1
- package/dist/web-mod-bundle.d.ts +14 -1
- package/dist/web-mod-bundle.js +68 -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 +7 -1
- package/dist/web-mod-store.js +21 -7
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-prefs.d.ts +14 -0
- package/dist/web-prefs.js +24 -4
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +6 -2
- package/dist/web-preview.js +4 -2
- package/dist/web-preview.js.map +1 -1
- package/dist/web-returning-device.d.ts +41 -0
- package/dist/web-returning-device.js +57 -5
- package/dist/web-returning-device.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 +70 -10
- package/dist/web-session.js +137 -22
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +49 -2
- package/dist/web-settings-patch.js +145 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +89 -0
- package/dist/web-settings-tools.js +270 -0
- package/dist/web-settings-tools.js.map +1 -0
- package/dist/web-shell-routes.d.ts +1 -1
- package/dist/web-shell-routes.js +1 -1
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-theme-store.d.ts +39 -0
- package/dist/web-theme-store.js +122 -11
- package/dist/web-theme-store.js.map +1 -1
- package/dist/web-theme.d.ts +2 -0
- package/dist/web-theme.js +10 -1
- package/dist/web-theme.js.map +1 -1
- package/dist/web-upload.d.ts +13 -0
- package/dist/web-upload.js +39 -3
- package/dist/web-upload.js.map +1 -1
- package/dist/workflow-templates/default.md +1 -1
- package/package.json +1 -1
- package/templates/systemd.service.ejs +8 -0
|
@@ -20,6 +20,7 @@ import { Empty, ErrorState, Skeleton } from "/assets/ui-states.js";
|
|
|
20
20
|
import { Icon } from "/assets/ui-icons.js";
|
|
21
21
|
import { Menu } from "/assets/ui-menu.js";
|
|
22
22
|
import { toast } from "/assets/ui-toast.js";
|
|
23
|
+
import { GroupMark, groupLabel, groupOf as navGroupOf, modelText } from "/assets/instance-nav.js";
|
|
23
24
|
import "./settings-strings.js";
|
|
24
25
|
import { api, attach, confirmedWrite, newKey } from "./settings-confirm.js";
|
|
25
26
|
import { dismissOperation, operationActive, restartFleet, startOperation, takeLeftover } from "./settings-apply.js";
|
|
@@ -30,7 +31,7 @@ import {
|
|
|
30
31
|
visibilityDefault, worstImpact,
|
|
31
32
|
} from "./settings-model.js";
|
|
32
33
|
import {
|
|
33
|
-
AgentDialog, BackendSelect, BotDialog, ChipList, ClassicDialog, Drawer, Feedback, Impact, Label, NewBotDialog, Override,
|
|
34
|
+
AgentDialog, BackendSelect, BotDialog, ChipList, ClassicDialog, Drawer, Feedback, Impact, Label, ModelSelect, NewBotDialog, Override,
|
|
34
35
|
Select, ask, impactText,
|
|
35
36
|
} from "./settings-dialogs.js";
|
|
36
37
|
import { SetupWizard } from "./settings-wizard.js";
|
|
@@ -42,8 +43,14 @@ import { setPreviewOptIn } from "./panel-chat.js";
|
|
|
42
43
|
import { startTour } from "./chat-tour.js";
|
|
43
44
|
import "./chat-strings.js";
|
|
44
45
|
import { AppearanceSection } from "./settings-appearance.js";
|
|
46
|
+
import { GuestsCard } from "./settings-guests.js";
|
|
47
|
+
import { WebAuthorityCard } from "./settings-web-authority.js";
|
|
45
48
|
|
|
46
49
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
50
|
+
/** What a refused change says to a person: the server's code in words (the code itself only for one not listed). */
|
|
51
|
+
const APPLY_ERRORS = new Set(["unsupported_sensitive_effect", "confirmation_diff_too_large", "session_required", "admin_required", "settings_changed",
|
|
52
|
+
"request_no_longer_current", "too_many_pending_changes", "fleet_unavailable", "invalid_settings_effect", "settings_confirmation_unavailable", "failed"]);
|
|
53
|
+
export const applyErrorText = (code) => (APPLY_ERRORS.has(code) ? tn(`applyError_${code}`) : tn("applyErrorOther", String(code)));
|
|
47
54
|
const ICONS = { device: "user", appearance: "palette", agents: "bot", bots: "plug", classic: "room", general: "sliders", web: "globe", advanced: "code" };
|
|
48
55
|
const RELEASES = "https://github.com/songsid/AgEnD/releases";
|
|
49
56
|
|
|
@@ -87,7 +94,7 @@ async function loadLive(lease) {
|
|
|
87
94
|
if (!lease.current()) return null;
|
|
88
95
|
const live = {}, classicLive = {};
|
|
89
96
|
if (f.ok && f.body && Array.isArray(f.body.instances)) for (const i of f.body.instances) {
|
|
90
|
-
live[i.name] = { status: i.status, state: i.state, backend: i.backend, model: i.model, model_display: i.model_display, effort: i.effort, effort_supported: i.effort_supported };
|
|
97
|
+
live[i.name] = { status: i.status, state: i.state, backend: i.backend, model: i.model, model_display: modelText(i, i.model_display), effort: i.effort, effort_supported: i.effort_supported };
|
|
91
98
|
if (i.classic) classicLive[i.name] = { status: i.status, state: i.state };
|
|
92
99
|
}
|
|
93
100
|
// /api/fleet carries live state for ClassicBot rooms; the profiles are the fallback for one not listed there yet.
|
|
@@ -457,7 +464,9 @@ function Agents({ ctx, search, openDialog }) {
|
|
|
457
464
|
const [collapsed, setCollapsed] = useState(() => new Set());
|
|
458
465
|
const [busy, setBusy] = useState(() => new Map()); // name → the action under way
|
|
459
466
|
const chs = channelsOf(ctx.fleet);
|
|
460
|
-
|
|
467
|
+
// #2172: the sidebar's grouping — an agent in a team under its team, else under its first tag.
|
|
468
|
+
const teamsOf = (name) => Object.keys(ctx.fleet.teams || {}).filter((t) => Array.isArray(ctx.fleet.teams[t]?.members) && ctx.fleet.teams[t].members.includes(name));
|
|
469
|
+
const groups = groupAgents(insts, ctx.live, search, (name) => navGroupOf({ tags: insts[name]?.tags, teams: teamsOf(name) }), groupLabel);
|
|
461
470
|
// One action per agent at a time: the whole row is off (Settings and ⋯ too), and the pressed button says it is working.
|
|
462
471
|
const run = async (name, action, work) => {
|
|
463
472
|
if (busy.has(name)) return;
|
|
@@ -473,7 +482,7 @@ function Agents({ ctx, search, openDialog }) {
|
|
|
473
482
|
return html`<section key=${group} class="s-group">
|
|
474
483
|
<button type="button" class="s-group-head" aria-expanded=${folded ? "false" : "true"}
|
|
475
484
|
onClick=${() => setCollapsed((c) => { const n = new Set(c); n.has(group) ? n.delete(group) : n.add(group); return n; })}>
|
|
476
|
-
<${Icon} name="chevron" size=${14} cls=${folded ? "caret folded" : "caret"} /><span class="grow">${group}</span><span class="count">${names.length}</span></button>
|
|
485
|
+
<${Icon} name="chevron" size=${14} cls=${folded ? "caret folded" : "caret"} /><${GroupMark} group=${group} /><span class="grow">${groupLabel(group)}</span><span class="count">${names.length}</span></button>
|
|
477
486
|
${folded ? null : names.map((name) => {
|
|
478
487
|
const inst = insts[name];
|
|
479
488
|
const st = (ctx.live[name] && ctx.live[name].status) || "stopped";
|
|
@@ -706,7 +715,8 @@ function General({ ctx }) {
|
|
|
706
715
|
<div class="field">${L(tn("language"), "defaults.locale", "g-locale")}<${Select} id="g-locale" value=${f.locale} onChange=${set("locale")}
|
|
707
716
|
options=${localeOptions(f.locale).map((o) => (o.auto ? { value: "", label: tn("localeAuto") } : o))} /></div>
|
|
708
717
|
<div class="field">${L(tn("defaultBackend"), "defaults.backend", "g-be")}<${BackendSelect} id="g-be" value=${f.backend} onChange=${set("backend")} /></div>
|
|
709
|
-
<div class="field">${L(tn("defaultModel"), "defaults.model", "g-model")}
|
|
718
|
+
<div class="field">${L(tn("defaultModel"), "defaults.model", "g-model")}<${ModelSelect} id="g-model" value=${f.model} onChange=${set("model")}
|
|
719
|
+
backend=${f.backend || "claude-code"} inherit=${tn("modelCliDefault")} /></div>
|
|
710
720
|
<div class="field">${L(`${tn("autoPause")} (${tn("minutes")})`, "defaults.auto_pause_after", "g-auto")}<input id="g-auto" type="number" min="0" step="1" class=${errors.auto ? "invalid" : ""} value=${f.auto} onInput=${(e) => set("auto")(e.target.value)} /><${Feedback} error=${errors.auto} /></div>
|
|
711
721
|
<div class="field">${L(tn("toolProgress"), "defaults.tool_progress", "g-tp")}<${Select} id="g-tp" value=${f.tool_progress} onChange=${set("tool_progress")} options=${TOOL_PROGRESS} /></div>
|
|
712
722
|
<div class="field">${L(tn("crossInstanceVisibility"), "defaults.cross_instance_visibility", "g-vis")}<${Select} id="g-vis" value=${f.visibility} onChange=${set("visibility")} options=${VISIBILITY_MODES} />
|
|
@@ -769,17 +779,20 @@ export const WEB_READ_ONLY = [
|
|
|
769
779
|
function WebSection({ ctx, staged }) {
|
|
770
780
|
const { schema } = ctx;
|
|
771
781
|
const web = ctx.fleet.web || {}, local = (web.session && web.session.local) || {}, term = ctx.fleet.web_terminal || {};
|
|
782
|
+
// Over the public link the fields read only; the Guests and Web authority cards are not offered (the server never
|
|
783
|
+
// serves their reads there).
|
|
772
784
|
const overPublicLink = globalThis.document?.body?.dataset?.publicLink === "1";
|
|
773
785
|
const original = useMemo(() => ({
|
|
774
786
|
echo: web.echo_to_channel ?? true,
|
|
775
787
|
log: web.log_tab === true, // #1218: off by default
|
|
788
|
+
usagePub: web.usage_report_on_public_link === true, // #2122: off by default
|
|
776
789
|
pub: { allow_public: (web.public_link && web.public_link.allow_public) ?? true, ttl_minutes: (web.public_link && web.public_link.ttl_minutes) ?? 120, protocol: (web.public_link && web.public_link.protocol) ?? "http2" },
|
|
777
790
|
session: { absolute_hours: local.absolute_hours ?? null, idle_hours: local.idle_hours ?? null },
|
|
778
791
|
terminal: { enabled: term.enabled ?? null, ttl_minutes: term.ttl_minutes ?? null },
|
|
779
792
|
}), []);
|
|
780
793
|
const text = (v) => (v === null || v === undefined ? "" : String(v));
|
|
781
794
|
const [f, setF] = useState(() => ({
|
|
782
|
-
echo: original.echo, log: original.log, pub: original.pub.allow_public, ttl: String(original.pub.ttl_minutes), protocol: original.pub.protocol,
|
|
795
|
+
echo: original.echo, log: original.log, usagePub: original.usagePub, pub: original.pub.allow_public, ttl: String(original.pub.ttl_minutes), protocol: original.pub.protocol,
|
|
783
796
|
abs: text(original.session.absolute_hours), idle: text(original.session.idle_hours),
|
|
784
797
|
termOn: original.terminal.enabled !== false, termTtl: text(original.terminal.ttl_minutes),
|
|
785
798
|
}));
|
|
@@ -808,6 +821,7 @@ function WebSection({ ctx, staged }) {
|
|
|
808
821
|
const body = {
|
|
809
822
|
...(f.echo !== original.echo ? { echo_to_channel: f.echo } : {}),
|
|
810
823
|
...(f.log !== original.log ? { log_tab: f.log } : {}),
|
|
824
|
+
...(f.usagePub !== original.usagePub ? { usage_report_on_public_link: f.usagePub } : {}),
|
|
811
825
|
...(Object.keys(publicPatch).length ? { public_link: publicPatch } : {}),
|
|
812
826
|
...(Object.keys(sessionPatch).length ? { session: { local: sessionPatch } } : {}),
|
|
813
827
|
...(Object.keys(terminalPatch).length ? { web_terminal: terminalPatch } : {}),
|
|
@@ -815,6 +829,7 @@ function WebSection({ ctx, staged }) {
|
|
|
815
829
|
const fields = [
|
|
816
830
|
...(body.echo_to_channel !== undefined ? ["web.echo_to_channel"] : []),
|
|
817
831
|
...(body.log_tab !== undefined ? ["web.log_tab"] : []),
|
|
832
|
+
...(body.usage_report_on_public_link !== undefined ? ["web.usage_report_on_public_link"] : []),
|
|
818
833
|
...Object.keys(publicPatch).map((k) => `web.public_link.${k}`),
|
|
819
834
|
...Object.keys(sessionPatch).map((k) => `web.session.local.${k}`),
|
|
820
835
|
...Object.keys(terminalPatch).map((k) => `web_terminal.${k}`),
|
|
@@ -856,6 +871,8 @@ function WebSection({ ctx, staged }) {
|
|
|
856
871
|
<p class="note">${tn("webEchoHint")}</p>
|
|
857
872
|
<label class="check"><input id="webLogTab" type="checkbox" checked=${f.log} onChange=${(e) => set("log")(e.target.checked)} /><span>${tn("webLogTab")}</span><${Impact} schema=${schema} field="web.log_tab" /></label>
|
|
858
873
|
<p class="note">${tn("webLogTabHint")}</p>
|
|
874
|
+
<label class="check"><input id="webUsagePublic" type="checkbox" checked=${f.usagePub} onChange=${(e) => set("usagePub")(e.target.checked)} /><span>${tn("webUsagePublic")}</span><${Impact} schema=${schema} field="web.usage_report_on_public_link" /></label>
|
|
875
|
+
<p class="note">${tn("webUsagePublicHint")}</p>
|
|
859
876
|
<label class="check"><input id="publicWebLink" type="checkbox" checked=${f.pub} onChange=${(e) => set("pub")(e.target.checked)} /><span>${tn("publicLink")}</span><${Impact} schema=${schema} field="web.public_link.allow_public" /></label>
|
|
860
877
|
<p class="note">${tn("publicLinkHint")}</p>
|
|
861
878
|
<div class="grid2">
|
|
@@ -864,6 +881,7 @@ function WebSection({ ctx, staged }) {
|
|
|
864
881
|
</div>
|
|
865
882
|
</section>
|
|
866
883
|
</fieldset>
|
|
884
|
+
${overPublicLink ? null : html`<${GuestsCard} ctx=${ctx} staged=${staged} /><${WebAuthorityCard} ctx=${ctx} staged=${staged} />`}
|
|
867
885
|
<section class="card s-web-readonly"><h3>${tn("webReadOnlyTitle")}</h3>
|
|
868
886
|
<p class="note">${tn("webReadOnlyLead")}</p>
|
|
869
887
|
<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>
|
|
@@ -941,7 +959,7 @@ function OperationCard({ op, schema }) {
|
|
|
941
959
|
const mismatch = schema && schema.fleet_signature_mismatch;
|
|
942
960
|
const title = op.phase === "failed" ? tn("applyPartial") : fleetRestarting ? tn("restartFleetTitle") : busy ? tn("applying")
|
|
943
961
|
: failedRows ? tn("applyPartial") : needsRestart ? tn("applyRestartNeeded") : tn("applyDone");
|
|
944
|
-
const note = op.phase === "failed" ? (op.error === "busy" ? tn("applyBusy") : op.error ? tn("applyStepFailed", op.error) : "")
|
|
962
|
+
const note = op.phase === "failed" ? (op.error === "busy" ? tn("applyBusy") : op.error ? tn("applyStepFailed", applyErrorText(op.error)) : "")
|
|
945
963
|
: fleetRestarting ? tn("restartFleetWaiting") : op.lostJob ? tn("applyLostJob")
|
|
946
964
|
: job && job.overdue ? tn("applyStillWorking", Math.round((job.elapsed_ms || 0) / 1000))
|
|
947
965
|
: mismatch && needsRestart ? tn("signatureMismatch", mismatch.join(", ")) : needsRestart ? tn("applyRestartHint") : "";
|
|
@@ -968,7 +986,7 @@ function OperationCard({ op, schema }) {
|
|
|
968
986
|
${op.steps.length ? html`<ul class="s-op-steps">${op.steps.map((s, i) => html`<li key=${i} class=${`s-step ${s.status}`}>
|
|
969
987
|
<span class="s-step-icon" aria-hidden="true">${icon(s.status) ? html`<${Icon} name=${icon(s.status)} size=${14} />` : "·"}</span>
|
|
970
988
|
<span class="grow">${s.label}</span>
|
|
971
|
-
<span class="note">${s.status === "waiting" ? tn("stepWaiting") : s.status === "skipped" ? tn("stepSkipped") : s.status === "failed" ? (s.error
|
|
989
|
+
<span class="note">${s.status === "waiting" ? tn("stepWaiting") : s.status === "skipped" ? tn("stepSkipped") : s.status === "failed" ? (s.error ? applyErrorText(s.error) : tn("applyFailed")) : s.note || impactText(s.impact)}</span></li>`)}</ul>` : null}
|
|
972
990
|
${rows.length ? html`<ul class="s-op-steps">${rows.map((r) => html`<li key=${r.target} class=${`s-step ${r.status}`}>
|
|
973
991
|
<span class="s-step-icon" aria-hidden="true">${r.status === "done" ? html`<${Icon} name="check" size=${14} />` : r.status === "failed" ? html`<${Icon} name="alert" size=${14} />` : r.status === "restart-required" ? html`<${Icon} name="restart" size=${14} />` : "·"}</span>
|
|
974
992
|
<span class="grow">${r.target === "fleet" ? "AgEnD" : shortName(r.target, {})}</span>
|
package/dist/ui/preview.js
CHANGED
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
notify();
|
|
101
101
|
return true;
|
|
102
102
|
}
|
|
103
|
-
if (j && (j.code === "publicLinkPreviewFailed" || j.code === "publicLink" || j.code === "fleetOff")) {
|
|
103
|
+
if (j && (j.code === "publicLinkPreviewFailed" || j.code === "publicLink" || j.code === "fleetOff" || j.code === "listenFailed" || j.code === "notListening")) {
|
|
104
104
|
cfg.reasonCode = j.code; cfg.reason = ""; cfg.detail = typeof j.detail === "string" ? j.detail : "";
|
|
105
105
|
notify();
|
|
106
106
|
return true;
|
|
@@ -191,7 +191,8 @@
|
|
|
191
191
|
if (!cfg.previewOrigin || !cfg.boot) {
|
|
192
192
|
// The server's reason, in the page's language when the server named it (a code), else as the server wrote it.
|
|
193
193
|
// #1723: a failed public-link preview names why ({0} is the reason, in the page's words).
|
|
194
|
-
var arg = cfg.reasonCode === "publicLinkPreviewFailed" ? say("pvFail_" + (cfg.detail || "unknown"), Object.prototype.hasOwnProperty.call(FAIL_EN, cfg.detail) ? FAIL_EN[cfg.detail] : "an unexpected error")
|
|
194
|
+
var arg = cfg.reasonCode === "publicLinkPreviewFailed" ? say("pvFail_" + (cfg.detail || "unknown"), Object.prototype.hasOwnProperty.call(FAIL_EN, cfg.detail) ? FAIL_EN[cfg.detail] : "an unexpected error")
|
|
195
|
+
: cfg.reasonCode === "listenFailed" ? cfg.detail : cfg.dashboardOrigin;
|
|
195
196
|
var english = cfg.reasonCode === "publicLinkPreviewFailed" ? "Previews are unavailable on this public link: {0}. The link itself keeps working."
|
|
196
197
|
: cfg.reason || "Previews are not available here.";
|
|
197
198
|
var server = cfg.reasonCode ? say("pvServer_" + cfg.reasonCode, english, arg) : "";
|
|
@@ -10,12 +10,14 @@ 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";
|
|
16
17
|
import { savePref } from "/assets/app-prefs.js";
|
|
17
18
|
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
18
|
-
import { colorsOff, defaultPlaces, PLACEMENTS, setColorsOff, setDefaultPlaces, useIdentityColors } from "/assets/identity-color.js";
|
|
19
|
+
import { colorsOff, defaultPlaces, identityClasses, identityOf, PLACEMENTS, paintIdentity, setColorsOff, setDefaultPlaces, useIdentityColors } from "/assets/identity-color.js";
|
|
20
|
+
import { CardTitle } from "./settings-learn.js";
|
|
19
21
|
import "./settings-strings.js";
|
|
20
22
|
|
|
21
23
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
@@ -42,22 +44,38 @@ function refusal(body) {
|
|
|
42
44
|
export function AppearanceSection() {
|
|
43
45
|
const publicLink = typeof document !== "undefined" && document.body && document.body.dataset.publicLink === "1";
|
|
44
46
|
if (publicLink) return html`<section class="card s-appearance"><h3>${tn("cssTitle")}</h3><p class="sub">${tn("cssPublicLink")}</p></section>`;
|
|
45
|
-
|
|
47
|
+
const group = (key) => html`<h2 class="s-ap-group">${tn(key)}</h2>`;
|
|
48
|
+
return html`${group("groupLook")}<${ThemesCard} /><${IdentityColorCard} /><${CardSizeCard} />
|
|
49
|
+
${group("groupLayout")}<${SidebarWidgetCard} />
|
|
50
|
+
${group("groupCss")}<${OwnerCssCard} /><${DeviceCard} />
|
|
51
|
+
${group("groupMods")}<${ModsCard} /><${EffectsCard} />
|
|
52
|
+
${group("groupAdvanced")}<${CardLimitsCard} /><${RestoreDefaultsCard} />`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** #2056 B: a sample sidebar row in the host's default colour places, so the choice is seen rather than described. */
|
|
56
|
+
const SAMPLE = { color: { person: "preset:teal" } };
|
|
57
|
+
function ColorSample({ places, off }) {
|
|
58
|
+
const id = identityOf(SAMPLE, { hostDefault: places, off });
|
|
59
|
+
return html`<div class="idc-preview s-colors-sample" aria-hidden="true"><span class=${`inst v-inst ${identityClasses(id)}`} ref=${(el) => paintIdentity(el, id)}>
|
|
60
|
+
<span class="av"><span class="av-text">A</span></span><span class="inst-text"><span class="inst-name">${tn("colorSample")}</span></span></span></div>`;
|
|
46
61
|
}
|
|
47
62
|
|
|
48
63
|
/**
|
|
49
|
-
* #1958: identity colours on this host — where they show for an instance nobody chose for (
|
|
50
|
-
* changed), and a switch that turns them off everywhere while every pick is kept.
|
|
64
|
+
* #1958: identity colours on this host — where they show for an instance nobody chose for (#2056: name, bar and
|
|
65
|
+
* ring unless changed), and a switch that turns them off everywhere while every pick is kept.
|
|
51
66
|
*/
|
|
52
67
|
function IdentityColorCard() {
|
|
53
68
|
useIdentityColors();
|
|
54
69
|
const places = defaultPlaces(), off = colorsOff();
|
|
55
70
|
const toggle = (p) => setDefaultPlaces(places.includes(p) ? places.filter((x) => x !== p) : [...places, p]);
|
|
56
|
-
return html`<section class="card s-appearance s-synced s-colors"
|
|
71
|
+
return html`<section class="card s-appearance s-synced s-colors"><${CardTitle} title=${tn("colorTitle")} topic="colors" />
|
|
57
72
|
<p class="sub">${tn("colorLead")}</p>
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
73
|
+
<div class="s-colors-row">
|
|
74
|
+
<fieldset class="idc-places" disabled=${off}><legend>${tn("colorPlaces")}</legend>
|
|
75
|
+
${PLACEMENTS.map((p) => html`<label key=${p} class="check"><input type="checkbox" checked=${places.includes(p)} onChange=${() => toggle(p)} /> ${t(`color.place_${p}`)}</label>`)}
|
|
76
|
+
</fieldset>
|
|
77
|
+
<${ColorSample} places=${places} off=${off} />
|
|
78
|
+
</div>
|
|
61
79
|
<label class="check"><input type="checkbox" class="s-colors-off" checked=${off} onChange=${(e) => setColorsOff(e.target.checked)} /> <span>${tn("colorOff")}</span></label>
|
|
62
80
|
<p class="sub">${tn("colorOffLead")}</p>
|
|
63
81
|
</section>`;
|
|
@@ -70,6 +88,7 @@ function IdentityColorCard() {
|
|
|
70
88
|
*/
|
|
71
89
|
export async function restoreAppearanceDefaults() {
|
|
72
90
|
setThemesShown(true);
|
|
91
|
+
setFollowsReducedMotion(false);
|
|
73
92
|
setCardSizeDefault("m");
|
|
74
93
|
savePref("card.sizeByInstance", null);
|
|
75
94
|
setSidebarWidget(null);
|
|
@@ -88,7 +107,7 @@ function RestoreDefaultsCard() {
|
|
|
88
107
|
setBusy(false);
|
|
89
108
|
if (r.ok) toast(tn("restoreDone")); else toast(tn("restoreCssFailed", r.error), false);
|
|
90
109
|
}
|
|
91
|
-
return html`<section class="card s-appearance s-restore"
|
|
110
|
+
return html`<section class="card s-appearance s-restore"><${CardTitle} title=${tn("restoreTitle")} topic="restore" />
|
|
92
111
|
<p class="sub">${tn("restoreLead")}</p>
|
|
93
112
|
<button type="button" class="btn" disabled=${busy} onClick=${go}>${tn("restoreTitle")}</button>
|
|
94
113
|
</section>`;
|
|
@@ -98,7 +117,7 @@ function RestoreDefaultsCard() {
|
|
|
98
117
|
function CardSizeCard() {
|
|
99
118
|
useStore(cardSizeStore);
|
|
100
119
|
const size = cardSizeDefault();
|
|
101
|
-
return html`<section class="card s-appearance s-synced"
|
|
120
|
+
return html`<section class="card s-appearance s-synced"><${CardTitle} title=${tn("cardSizeTitle")} topic="cards" />
|
|
102
121
|
<div class="card-size" role="group" aria-label=${tn("cardSizeTitle")}>
|
|
103
122
|
${CARD_SIZES.map((s) => html`<button key=${s} type="button" class=${`seg-item${s === size ? " active" : ""}`} aria-pressed=${s === size ? "true" : "false"}
|
|
104
123
|
onClick=${() => setCardSizeDefault(s)}>${t(`card.size${s.toUpperCase()}Name`)}</button>`)}
|
|
@@ -131,7 +150,7 @@ function CardLimitsCard() {
|
|
|
131
150
|
};
|
|
132
151
|
useEffect(() => { load(); }, []);
|
|
133
152
|
if (!view) return null;
|
|
134
|
-
if (view.error) return html`<section class="card s-appearance"
|
|
153
|
+
if (view.error) return html`<section class="card s-appearance"><${CardTitle} title=${tn("cardLimitsTitle")} topic="limits" /><p class="sub">${tn("cardLimitsUnavailable")}</p></section>`;
|
|
135
154
|
const configured = (g, k) => (view.configured && view.configured[g] && typeof view.configured[g][k] === "number" ? view.configured[g][k] : null);
|
|
136
155
|
const save = async () => {
|
|
137
156
|
const body = {};
|
|
@@ -148,7 +167,7 @@ function CardLimitsCard() {
|
|
|
148
167
|
if (r.ok) { setMsg(tn("cardLimitsSaved")); load(); } else setMsg(tn("cardLimitsFailed", (r.body && r.body.error) || r.status));
|
|
149
168
|
};
|
|
150
169
|
const warnings = Array.isArray(view.warnings) ? view.warnings : [];
|
|
151
|
-
return html`<section class="card s-appearance"
|
|
170
|
+
return html`<section class="card s-appearance"><${CardTitle} title=${tn("cardLimitsTitle")} topic="limits" />
|
|
152
171
|
<p class="sub">${tn("cardLimitsLead")}</p>
|
|
153
172
|
<fieldset class="form" disabled=${busy}>
|
|
154
173
|
${["portrait", "banner", "instance_quota"].map((g) => html`<div key=${g} class="grid3">
|
|
@@ -170,10 +189,16 @@ function CardLimitsCard() {
|
|
|
170
189
|
/** #1629 r2 (d): whether this person's screens show agents' themes (and, as they come, effects) — synced. */
|
|
171
190
|
function ThemesCard() {
|
|
172
191
|
const s = useStore(themesStore);
|
|
173
|
-
|
|
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())), []);
|
|
195
|
+
return html`<section class="card s-appearance s-synced"><${CardTitle} title=${tn("themesTitle")} topic="themes" />
|
|
174
196
|
<label class="check"><input type="checkbox" checked=${s.shown} disabled=${safeMode()} onChange=${(e) => setThemesShown(e.target.checked)} />
|
|
175
197
|
<span>${tn("themesShow")}</span></label>
|
|
176
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>
|
|
177
202
|
</section>`;
|
|
178
203
|
}
|
|
179
204
|
|
|
@@ -209,7 +234,7 @@ function SidebarWidgetCard() {
|
|
|
209
234
|
toast(tn("widgetSaved"));
|
|
210
235
|
};
|
|
211
236
|
const remove = () => { setSidebarWidget(null); setText(""); setError(""); toast(tn("widgetRemoved")); };
|
|
212
|
-
return html`<section class="card s-appearance s-synced s-widget"
|
|
237
|
+
return html`<section class="card s-appearance s-synced s-widget"><${CardTitle} title=${tn("widgetTitle")} topic="widgets" />
|
|
213
238
|
<p class="sub">${tn("widgetLead")}</p>
|
|
214
239
|
<label class="field"><span class="sr-only">${tn("widgetTitle")}</span>
|
|
215
240
|
<textarea class="s-widget-text" rows="10" spellcheck=${false} autocapitalize="off" autocomplete="off" value=${text}
|
|
@@ -240,8 +265,8 @@ function OwnerCssCard() {
|
|
|
240
265
|
useEffect(() => { load(); }, []);
|
|
241
266
|
// A trial answered (kept or reverted) changes what is in force: read it again, keeping the editor's text.
|
|
242
267
|
useEffect(() => { if (state) load({ text: false }); }, [trial && trial.sha]);
|
|
243
|
-
if (!state) return html`<section class="card s-appearance"
|
|
244
|
-
if (state.failed) return html`<section class="card s-appearance"
|
|
268
|
+
if (!state) return html`<section class="card s-appearance"><${CardTitle} title=${tn("cssTitle")} topic="css" /><p class="sub">…</p></section>`;
|
|
269
|
+
if (state.failed) return html`<section class="card s-appearance"><${CardTitle} title=${tn("cssTitle")} topic="css" /><p class="err">${tn("cssLoadFailed")}</p></section>`;
|
|
245
270
|
|
|
246
271
|
const save = async () => {
|
|
247
272
|
setBusy(true); setError("");
|
|
@@ -285,7 +310,7 @@ function OwnerCssCard() {
|
|
|
285
310
|
? (state.pending ? tn("cssOnTrial", short(state.active)) : tn("cssInForce", short(state.active)))
|
|
286
311
|
: tn("cssNone");
|
|
287
312
|
const assetBytes = state.assets.reduce((n, a) => n + a.bytes, 0);
|
|
288
|
-
return html`<section class="card s-appearance s-owner-css"
|
|
313
|
+
return html`<section class="card s-appearance s-owner-css"><${CardTitle} title=${tn("cssTitle")} topic="css" />
|
|
289
314
|
<p class="sub">${tn("cssLead")}</p>
|
|
290
315
|
<p class="s-css-status" role="status">${inForce}</p>
|
|
291
316
|
<label class="field"><span class="sr-only">${tn("cssTitle")}</span>
|
|
@@ -319,7 +344,6 @@ function DeviceCard() {
|
|
|
319
344
|
return html`<section class="card s-appearance s-local"><h3>${tn("cssDeviceTitle")}</h3>
|
|
320
345
|
<label class="check"><input type="checkbox" checked=${off} onChange=${(e) => { setOwnerCssOffHere(e.target.checked); setOff(ownerCssOffHere()); }} />
|
|
321
346
|
<span>${tn("cssDeviceOff")}</span></label>
|
|
322
|
-
<p class="sub">${tn("cssDeviceOffLead")}</p>
|
|
323
347
|
<p class="sub">${tn("cssSafe")}</p>
|
|
324
348
|
</section>`;
|
|
325
349
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// #2072 part 3: what a backend or model field in Settings offers — the host's published availability (installed,
|
|
2
|
+
// signed in, new enough: /ui/backends) and the model catalog of that backend for that instance's account (/ui/models).
|
|
3
|
+
// Both answers are the server's cached ones; nothing here makes the server probe. A page asks once and reuses the answer
|
|
4
|
+
// for a short while, so opening several dialogs costs one request each.
|
|
5
|
+
import { useEffect, useState } from "/assets/app-html.js";
|
|
6
|
+
|
|
7
|
+
const TTL_MS = 30_000;
|
|
8
|
+
const cache = new Map(); // url → { at, value, pending }
|
|
9
|
+
|
|
10
|
+
async function getJson(url) {
|
|
11
|
+
const res = await fetch(url, { credentials: "same-origin" });
|
|
12
|
+
if (!res.ok) throw new Error(String(res.status));
|
|
13
|
+
return res.json();
|
|
14
|
+
}
|
|
15
|
+
/** The answer for `url`, shared by every field on the page while it is fresh; null when it cannot be read. */
|
|
16
|
+
function cached(url) {
|
|
17
|
+
const hit = cache.get(url), now = performance.now();
|
|
18
|
+
if (hit && (hit.pending || now - hit.at < TTL_MS)) return hit.pending ?? Promise.resolve(hit.value);
|
|
19
|
+
const pending = getJson(url).then((value) => { cache.set(url, { at: performance.now(), value, pending: null }); return value; })
|
|
20
|
+
.catch(() => { cache.delete(url); return null; });
|
|
21
|
+
cache.set(url, { at: now, value: null, pending });
|
|
22
|
+
return pending;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Drop every remembered answer. Only tests call it today: a page keeps an answer TTL_MS, so a sign-in or a newly
|
|
26
|
+
* cached catalog shows within that time, or at once on the next page load.
|
|
27
|
+
*/
|
|
28
|
+
export function forgetCatalogs() { cache.clear(); }
|
|
29
|
+
|
|
30
|
+
function useAnswer(url) {
|
|
31
|
+
const [value, setValue] = useState(undefined); // undefined: loading; null: unavailable
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!url) { setValue(null); return undefined; }
|
|
34
|
+
let live = true;
|
|
35
|
+
setValue(undefined);
|
|
36
|
+
cached(url).then((v) => { if (live) setValue(v); });
|
|
37
|
+
return () => { live = false; };
|
|
38
|
+
}, [url]);
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The host's backends with their state ({ name, state, minVersion, … }), or null while loading or unavailable. */
|
|
43
|
+
export function useBackendRows() {
|
|
44
|
+
const answer = useAnswer("/ui/backends");
|
|
45
|
+
return answer && Array.isArray(answer.backends) ? answer.backends : answer === undefined ? undefined : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The model catalog of `backend` for `instance` (its account) — [] when none is cached; undefined while loading. */
|
|
49
|
+
export function useModelCatalog(backend, instance) {
|
|
50
|
+
const url = backend ? `/ui/models?backend=${encodeURIComponent(backend)}${instance ? `&instance=${encodeURIComponent(instance)}` : ""}` : null;
|
|
51
|
+
const answer = useAnswer(url);
|
|
52
|
+
if (answer === undefined) return undefined;
|
|
53
|
+
return answer && Array.isArray(answer.models) ? answer.models : [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** A state that keeps a backend from being chosen (shown, disabled, with why); "unknown" never does. */
|
|
57
|
+
export const BLOCKING_STATES = ["not_installed", "not_signed_in", "too_old"];
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The backend select's options: every backend the page knows, in its order, each with what the host said about it.
|
|
61
|
+
* The current value is always selectable — a probe never disables what an agent already runs — and one the page does
|
|
62
|
+
* not know (a removed backend) stays first, as before.
|
|
63
|
+
*/
|
|
64
|
+
export function backendChoices(known, rows, value, words) {
|
|
65
|
+
const byName = new Map((rows || []).map((r) => [r.name, r]));
|
|
66
|
+
const choice = (name) => {
|
|
67
|
+
const row = byName.get(name), state = row ? row.state ?? (row.installed ? "usable" : row.unknown ? "unknown" : "not_installed") : null;
|
|
68
|
+
const blocked = !!state && BLOCKING_STATES.includes(state) && name !== value;
|
|
69
|
+
const note = state === "not_installed" ? words.notInstalled : state === "not_signed_in" ? words.notSignedIn
|
|
70
|
+
: state === "too_old" ? words.tooOld(row.minVersion ?? "?") : state === "unknown" && name !== value ? words.unchecked : "";
|
|
71
|
+
return { value: name, label: note ? `${name} — ${note}` : name, disabled: blocked };
|
|
72
|
+
};
|
|
73
|
+
const list = known.map(choice);
|
|
74
|
+
return known.includes(value) || !value ? list : [{ value, label: words.removed(value), disabled: false }, ...list];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The id of the "type another model" choice (never a model name: it has a space). */
|
|
78
|
+
export const OTHER_MODEL = "\u0000other";
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The model select's options: the inherited choice (empty value), the current model first when the catalog does not
|
|
82
|
+
* list it (marked), the catalog, and "Other…" for a name the CLI does not list.
|
|
83
|
+
*/
|
|
84
|
+
export function modelChoices(catalog, value, words, inherit) {
|
|
85
|
+
const list = [];
|
|
86
|
+
if (inherit) list.push({ value: "", label: inherit });
|
|
87
|
+
const ids = new Set((catalog || []).map((m) => m.id));
|
|
88
|
+
if (value && !ids.has(value)) list.push({ value, label: `${value} — ${words.notListed}` });
|
|
89
|
+
for (const m of catalog || []) list.push({ value: m.id, label: m.description ? `${m.label || m.id} — ${m.description}` : (m.label || m.id) });
|
|
90
|
+
list.push({ value: OTHER_MODEL, label: words.other });
|
|
91
|
+
return list;
|
|
92
|
+
}
|
|
@@ -13,11 +13,13 @@ import { api, confirmedWrite, newKey } from "./settings-confirm.js";
|
|
|
13
13
|
import { adminUserIdError, adminUserIdPattern, botHandle, TokenEnvNote, TokenField, verifyBotToken } from "./settings-token.js";
|
|
14
14
|
import { DiscordServerPicker } from "./settings-discord.js";
|
|
15
15
|
import { startOperation } from "./settings-apply.js";
|
|
16
|
+
import "./settings-strings.js"; // its words, wherever a picker of this file is used (#2072: Fleet's create dialog too)
|
|
17
|
+
import { backendChoices, modelChoices, OTHER_MODEL, useBackendRows, useModelCatalog } from "./settings-catalog.js";
|
|
16
18
|
import {
|
|
17
19
|
ACCESS_MODES, AGENT_FIELDS, AGENT_MODES, BOT_FIELDS, CH_TYPES, CLASSIC_FIELDS, HANG_TIMEOUT_DEFAULT_MINUTES, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS,
|
|
18
20
|
VISIBILITY_MODES, accessConfirmations, agentForm, agentPatch, backendOptions, changedFields, channelId, channelsOf,
|
|
19
21
|
chLabel, hasOwn, impactOf, nonNegative, nonNegativeInteger, positive, replyGuardSupported, sameValue, visibilityDefault,
|
|
20
|
-
|
|
22
|
+
classicWebEchoEffective, worstImpact,
|
|
21
23
|
} from "./settings-model.js";
|
|
22
24
|
|
|
23
25
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
@@ -32,9 +34,36 @@ export function Select({ value, options, onChange, disabled, id, label }) {
|
|
|
32
34
|
return html`<select id=${id} value=${value} disabled=${disabled} aria-label=${label} onChange=${(e) => onChange(e.target.value)}>
|
|
33
35
|
${opts.map((o) => html`<option key=${o.value} value=${o.value}>${o.label}</option>`)}</select>`;
|
|
34
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* #2072: a backend field shows every backend with what this host says about it — one it cannot run is disabled with
|
|
39
|
+
* why (not installed, not signed in, too old); one not checked yet stays selectable; the current value always can be.
|
|
40
|
+
*/
|
|
35
41
|
export function BackendSelect({ value, onChange, disabled, id }) {
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
const rows = useBackendRows();
|
|
43
|
+
const known = backendOptions(value).filter((o) => !o.unavailable).map((o) => o.value);
|
|
44
|
+
const opts = backendChoices(known, rows || [], value, {
|
|
45
|
+
notInstalled: tn("backendNotInstalled"), notSignedIn: tn("backendNotSignedIn"), tooOld: (v) => tn("backendTooOld", v),
|
|
46
|
+
unchecked: tn("backendUnchecked"), removed: (v) => tn("backendUnavailable", v),
|
|
47
|
+
});
|
|
48
|
+
return html`<select id=${id} value=${value} disabled=${disabled} onChange=${(e) => onChange(e.target.value)}>
|
|
49
|
+
${opts.map((o) => html`<option key=${o.value} value=${o.value} disabled=${o.disabled}>${o.label}</option>`)}</select>`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* #2072: a model field is a list — that backend's models for this account (cached; never a probe), the current one
|
|
53
|
+
* first and marked when the CLI does not list it, and "Other…" for a name it does not list (a text box opens with
|
|
54
|
+
* the current value in it: choosing "Other…" alone changes nothing — emptying the box is what inherits, #2098 review).
|
|
55
|
+
* `inherit`: the label of the empty choice (inherit / none), or omit it when the field has none.
|
|
56
|
+
*/
|
|
57
|
+
export function ModelSelect({ value, onChange, disabled, id, backend, instance, inherit }) {
|
|
58
|
+
const catalog = useModelCatalog(backend, instance);
|
|
59
|
+
const [typing, setTyping] = useState(false);
|
|
60
|
+
const opts = modelChoices(catalog || [], value, { notListed: tn("modelNotListed"), other: tn("modelOther") }, inherit);
|
|
61
|
+
return html`<div class="model-pick">
|
|
62
|
+
<select id=${id} value=${typing ? OTHER_MODEL : value} disabled=${disabled}
|
|
63
|
+
onChange=${(e) => { const v = e.target.value; if (v === OTHER_MODEL) setTyping(true); else { setTyping(false); onChange(v); } }}>
|
|
64
|
+
${opts.map((o) => html`<option key=${o.value} value=${o.value}>${o.label}</option>`)}</select>
|
|
65
|
+
${typing ? html`<input type="text" aria-label=${tn("modelOtherLabel")} placeholder=${tn("modelOtherPlaceholder")} disabled=${disabled}
|
|
66
|
+
value=${value} onInput=${(e) => onChange(e.target.value)} />` : null}</div>`;
|
|
38
67
|
}
|
|
39
68
|
export function Impact({ schema, field }) { return html`<span class="impact">${impactText(impactOf(schema, field))}</span>`; }
|
|
40
69
|
export function Label({ text, schema, field, htmlFor }) {
|
|
@@ -228,7 +257,8 @@ export function AgentDialog({ name, inst, ctx, onClose }) {
|
|
|
228
257
|
<input id="ag-dn" type="text" placeholder=${name} disabled=${f.inherit.display_name} value=${shown("display_name", f.display_name, defaults.display_name || "")} onInput=${(e) => set("display_name")(e.target.value)} /></${Override}></div>
|
|
229
258
|
<div class="field">${L(tn("description"), "instance.description", "ag-desc")}<input id="ag-desc" type="text" value=${f.description} onInput=${(e) => set("description")(e.target.value)} /></div>
|
|
230
259
|
<div class="field">${L(tn("backend"), "instance.backend", "ag-be")}<${BackendSelect} id="ag-be" value=${f.backend} onChange=${set("backend")} /></div>
|
|
231
|
-
<div class="field">${L(tn("model"), "instance.model", "ag-model")}
|
|
260
|
+
<div class="field">${L(tn("model"), "instance.model", "ag-model")}<${ModelSelect} id="ag-model" value=${f.model} onChange=${set("model")}
|
|
261
|
+
backend=${f.backend || defaults.backend || "claude-code"} instance=${name} inherit=${tn("inheritPlaceholder")} /></div>
|
|
232
262
|
</div>
|
|
233
263
|
<div class="field">${L(tn("workingDir"), "instance.working_directory", "ag-wd")}<input id="ag-wd" type="text" value=${f.working_directory} onInput=${(e) => set("working_directory")(e.target.value)} /></div>
|
|
234
264
|
<div class="field">${L(`${tn("autoPause")} (${tn("minutes")})`, "instance.auto_pause_after", "ag-auto")}
|
|
@@ -264,8 +294,8 @@ export function AgentDialog({ name, inst, ctx, onClose }) {
|
|
|
264
294
|
onChange=${(e) => set("lightweight")(e.target.checked)} /><span>${tn("lightweight")}</span></label></${Override}></div>
|
|
265
295
|
<div class="field">${L(tn("modelFailover"), "instance.model_failover", "ag-fo")}
|
|
266
296
|
<${Override} inherit=${f.inherit.model_failover} onInherit=${setInherit("model_failover")}>
|
|
267
|
-
|
|
268
|
-
value=${shown("model_failover", f.model_failover, (defaults.model_failover && defaults.model_failover[0]) || "")}
|
|
297
|
+
<${ModelSelect} id="ag-fo" disabled=${f.inherit.model_failover} backend=${f.backend || defaults.backend || "claude-code"} instance=${name} inherit=${tn("modelNone")}
|
|
298
|
+
value=${shown("model_failover", f.model_failover, (defaults.model_failover && defaults.model_failover[0]) || "")} onChange=${set("model_failover")} /></${Override}>
|
|
269
299
|
<${Feedback} warning=${failoverWarn} /></div>
|
|
270
300
|
</div>
|
|
271
301
|
<div class="field">${L(tn("systemPrompt"), "instance.systemPrompt", "ag-sys")}<input id="ag-sys" type="text" placeholder="e.g. file:prompts/dev.md" value=${f.systemPrompt} onInput=${(e) => set("systemPrompt")(e.target.value)} /></div>
|
|
@@ -557,7 +587,7 @@ export function ClassicDialog({ room, ctx, onClose }) {
|
|
|
557
587
|
backend: c.backend || inherited.backend, model: c.model || "",
|
|
558
588
|
auto_pause_after: String(c.auto_pause_after ?? inherited.auto_pause_after), tool_progress: c.tool_progress ?? inherited.tool_progress,
|
|
559
589
|
reply_completion_guard: c.reply_completion_guard ?? inherited.reply_completion_guard,
|
|
560
|
-
collab: !!c.collab, web_echo:
|
|
590
|
+
collab: !!c.collab, web_echo: classicWebEchoEffective(c, fleet), context_lines: String(c.context_lines ?? cd.context_lines ?? 5),
|
|
561
591
|
inherit: { auto_pause_after: !hasOwn(c, "auto_pause_after"), tool_progress: !hasOwn(c, "tool_progress"), reply_completion_guard: !hasOwn(c, "reply_completion_guard") },
|
|
562
592
|
}));
|
|
563
593
|
const set = (k) => (v) => setF((x) => ({ ...x, [k]: v }));
|
|
@@ -580,12 +610,9 @@ export function ClassicDialog({ room, ctx, onClose }) {
|
|
|
580
610
|
auto_pause_after: hasOwn(c, "auto_pause_after") ? c.auto_pause_after : null,
|
|
581
611
|
tool_progress: hasOwn(c, "tool_progress") ? c.tool_progress : null,
|
|
582
612
|
reply_completion_guard: hasOwn(c, "reply_completion_guard") ? c.reply_completion_guard : null,
|
|
583
|
-
collab: !!c.collab, context_lines: c.context_lines ?? cd.context_lines ?? 5, web_echo:
|
|
613
|
+
collab: !!c.collab, context_lines: c.context_lines ?? cd.context_lines ?? 5, web_echo: classicWebEchoEffective(c, fleet),
|
|
584
614
|
});
|
|
585
615
|
if (!Object.keys(patch).length) { onClose(); return; }
|
|
586
|
-
// #1320: turning web echo on for a group posts every web message where all its members can read it.
|
|
587
|
-
if (patch.web_echo === true && !c.web_echo && webEchoIsGroup(c.channelId)
|
|
588
|
-
&& !(await ask(tn("webEchoGroupConfirm"), { confirmLabel: tn("webEchoEnable") }))) return;
|
|
589
616
|
if (!lease.current()) return;
|
|
590
617
|
ctx.stageClassic(c, patch);
|
|
591
618
|
onClose();
|
|
@@ -594,7 +621,8 @@ export function ClassicDialog({ room, ctx, onClose }) {
|
|
|
594
621
|
return html`<${EditDialog} title=${c.name || c.instanceName} subtitle=${`ID: ${c.instanceName} · ${tn(`exec_${runtime.execution}`)}`} onClose=${onClose} onStage=${stage} ready=${ready} impacts=${CLASSIC_FIELDS} schema=${schema}>
|
|
595
622
|
<div class="grid2">
|
|
596
623
|
<div class="field">${L(tn("backend"), "classic.backend", "cl-be")}<${BackendSelect} id="cl-be" value=${f.backend} onChange=${set("backend")} /></div>
|
|
597
|
-
<div class="field">${L(tn("model"), "classic.model", "cl-model")}
|
|
624
|
+
<div class="field">${L(tn("model"), "classic.model", "cl-model")}<${ModelSelect} id="cl-model" value=${f.model} onChange=${set("model")}
|
|
625
|
+
backend=${f.backend} instance=${c.instanceName} inherit=${inherited.model ? tn("inheritValue", inherited.model) : tn("inheritPlaceholder")} /></div>
|
|
598
626
|
<div class="field">${L(`${tn("autoPause")} (${tn("minutes")})`, "classic.auto_pause_after", "cl-auto")}
|
|
599
627
|
<${Override} inherit=${f.inherit.auto_pause_after} onInherit=${setInherit("auto_pause_after")}>
|
|
600
628
|
<input id="cl-auto" type="number" min="0" step="1" class=${errors.auto ? "invalid" : ""} disabled=${f.inherit.auto_pause_after}
|
|
@@ -4,10 +4,11 @@ import { register, t } from "/assets/app-i18n.js";
|
|
|
4
4
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
5
5
|
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
6
6
|
import { OwnerEffects } from "./settings-owner-effects.js";
|
|
7
|
+
import { CardTitle } from "./settings-learn.js";
|
|
7
8
|
|
|
8
9
|
register("effectSettings", {
|
|
9
10
|
en: {
|
|
10
|
-
title: "Effects", lead: "
|
|
11
|
+
title: "Effects", lead: "Your effect files, and which one a chat uses.", folder: "Upload an effect folder", files: "effect.json + worker.js (and pictures), up to 1 MiB.",
|
|
11
12
|
inUse: "This effect is still in use and cannot be deleted.", quota: "Your library reached its 10-effect or 8 MiB limit. Delete unused copies first.", rateLimited: "Too many changes. Wait a moment, then try again.",
|
|
12
13
|
loading: "Loading effects…", retry: "Reload library", empty: "No effects in your library.", failed: "Couldn't load the library. Try again.", actionFailed: "Couldn't save this change. Try again.", savedRefresh: "Saved. Reload the library to see the result.",
|
|
13
14
|
uploadFailed: "Couldn't upload this folder. Check its files and size, then try again.", public: "Effect management isn't available on a temporary public link.", readOnly: "Read-only session: sign in as an admin to change effects.",
|
|
@@ -16,7 +17,7 @@ register("effectSettings", {
|
|
|
16
17
|
fps: "Host FPS p50 / p10", tasks: "Host long task p95", noTasks: "Not observed", standard: "Standard device", constrained: "Constrained device", params: "Parameters", badParams: "Check the parameter values.", more: "Show more", count: "{0} effects", one: "1 effect", profileFailed: "Couldn't read profile history. Try again.",
|
|
17
18
|
},
|
|
18
19
|
"zh-TW": {
|
|
19
|
-
title: "效果", lead: "
|
|
20
|
+
title: "效果", lead: "你的效果檔案,以及每個聊天使用哪一個。", folder: "上傳效果資料夾", files: "effect.json + worker.js(可加圖片),最多 1 MiB。",
|
|
20
21
|
inUse: "這個效果仍在使用中,無法刪除。", quota: "你的程式庫已達 10 個效果或 8 MiB 上限,請先刪除未使用的副本。", rateLimited: "變更太頻繁,請稍候再試。",
|
|
21
22
|
loading: "正在載入效果…", retry: "重新載入程式庫", empty: "程式庫中還沒有效果。", failed: "無法載入程式庫,請重試。", actionFailed: "無法儲存變更,請重試。", savedRefresh: "已儲存,請重新載入程式庫查看結果。",
|
|
22
23
|
uploadFailed: "無法上傳這個資料夾,請檢查檔案與大小後重試。", public: "臨時公開連結不提供效果管理。", readOnly: "目前為唯讀登入,請以管理員身分登入才能修改效果。",
|
|
@@ -180,7 +181,7 @@ export function EffectsCard() {
|
|
|
180
181
|
if (!current(op.seq)) return; finish(op.seq);
|
|
181
182
|
if (yes) await change(`/ui/effects/${id}`, undefined, "DELETE");
|
|
182
183
|
};
|
|
183
|
-
return html`<section class="card s-appearance s-effects"
|
|
184
|
+
return html`<section class="card s-appearance s-effects"><${CardTitle} title=${tn("title")} topic="effects" /><p class="sub">${tn("lead")}</p>
|
|
184
185
|
<div class="s-fx-actions"><button type="button" class="btn" disabled=${busy} onClick=${reload}>${tn("retry")}</button>
|
|
185
186
|
${view?.canWrite ? html`<button class="btn s-fx-choose" type="button" disabled=${busy} onClick=${() => uploadInput.current?.click()}>${tn("folder")}</button><input ref=${uploadInput} class="offscreen s-fx-upload" aria-label=${tn("folder")} tabindex="-1" type="file" webkitdirectory=${true} directory=${true} multiple disabled=${busy} onChange=${upload} />` : null}</div>
|
|
186
187
|
${busy ? html`<p class="sub" role="status">${tn("loading")}</p>` : null}${error ? html`<p class="err" role="status">${error}</p>` : null}${message ? html`<p class="sub" role="status">${message}</p>` : null}
|