@songsid/agend 2.3.0-beta.1 → 2.3.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-cli.js +5 -5
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-file-read.d.ts +11 -6
- package/dist/agent-file-read.js +32 -17
- package/dist/agent-file-read.js.map +1 -1
- package/dist/agent-whats-new.js +10 -13
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.js +10 -5
- package/dist/auth-api.js.map +1 -1
- 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 +13 -0
- package/dist/backend/types.js +10 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/cache-analysis.d.ts +47 -1
- package/dist/cache-analysis.js +68 -8
- package/dist/cache-analysis.js.map +1 -1
- package/dist/cache-report-tool.d.ts +17 -0
- package/dist/cache-report-tool.js +40 -0
- package/dist/cache-report-tool.js.map +1 -0
- package/dist/cache-service.d.ts +15 -0
- package/dist/cache-service.js +26 -1
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/mcp-tools.js +9 -3
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/classic-channel-manager.d.ts +8 -3
- package/dist/classic-channel-manager.js +10 -3
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +7 -3
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +5 -1
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.js +5 -5
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.js +5 -0
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/fleet-manager.d.ts +71 -3
- package/dist/fleet-manager.js +256 -45
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-traffic.d.ts +38 -0
- package/dist/fleet-traffic.js +96 -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/multi-channel/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +149 -5
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +398 -0
- package/dist/locale.js +6 -4
- package/dist/locale.js.map +1 -1
- 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 +4 -0
- package/dist/outbound-handlers.js +74 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +98 -0
- package/dist/outbound-schemas.js +56 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +1 -1
- package/dist/public-web-gateway.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/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 +5 -0
- package/dist/tool-permissions.js +15 -2
- package/dist/tool-permissions.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/ui/app-effects.js +33 -9
- package/dist/ui/chat-effects.js +62 -4
- package/dist/ui/chat-strings.js +40 -4
- package/dist/ui/chat-thread.js +13 -5
- 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 +489 -0
- package/dist/ui/fleet-org.js +27 -12
- package/dist/ui/fleet-traffic.js +224 -0
- package/dist/ui/panel-chat.js +8 -2
- package/dist/ui/panel-details.js +2 -1
- package/dist/ui/panel-fleet.js +17 -5
- package/dist/ui/panel-settings.js +3 -2
- package/dist/ui/preview.js +3 -2
- package/dist/ui/settings-appearance.js +34 -18
- package/dist/ui/settings-dialogs.js +3 -6
- package/dist/ui/settings-effects.js +4 -3
- package/dist/ui/settings-learn.js +25 -0
- package/dist/ui/settings-model.js +6 -2
- package/dist/ui/settings-mods.js +4 -3
- package/dist/ui/settings-owner-effects.js +8 -8
- package/dist/ui/settings-strings.js +34 -38
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-prefs.js +1 -1
- package/dist/ui/shared/app-shell.js +23 -1
- package/dist/ui/shared/app-store.js +12 -1
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +163 -12
- package/dist/ui/shared/effect-app-runtime.js +1 -1
- package/dist/ui/shared/effect-frame.js +10 -5
- package/dist/ui/shared/effect-geometry.js +24 -9
- package/dist/ui/shared/effect-policy.js +2 -1
- package/dist/ui/shared/effect-registry.js +71 -17
- package/dist/ui/shared/effect-trial-shadow.js +62 -0
- package/dist/ui/shared/effect-trial-stage.js +15 -9
- package/dist/ui/shared/effect-trial.css +40 -0
- package/dist/ui/shared/identity-card.js +5 -2
- package/dist/ui/shared/identity-color.js +3 -3
- package/dist/ui/shared/instance-nav.js +35 -9
- package/dist/ui/shared/panel-view.js +2 -2
- package/dist/ui/shared/usage-dialog.js +14 -2
- package/dist/ui/shared/view-strings.js +22 -2
- package/dist/ui/signin.html +1 -1
- package/dist/usage/providers.d.ts +3 -0
- package/dist/usage/providers.js +4 -3
- package/dist/usage/providers.js.map +1 -1
- package/dist/view-api.d.ts +21 -0
- package/dist/view-api.js +28 -0
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +5 -6
- package/dist/web-api.js +60 -11
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-view.d.ts +10 -0
- package/dist/web-appearance-view.js +77 -8
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-asset-parts.d.ts +3 -8
- package/dist/web-asset-parts.js.map +1 -1
- package/dist/web-auth.js +1 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-effect-service.js +3 -1
- package/dist/web-effect-service.js.map +1 -1
- package/dist/web-effect-tools.js +4 -1
- 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.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 +67 -0
- package/dist/web-guest-access.js +126 -0
- package/dist/web-guest-access.js.map +1 -0
- 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-mod-agent.js +5 -3
- package/dist/web-mod-agent.js.map +1 -1
- package/dist/web-mod-bundle.d.ts +5 -0
- package/dist/web-mod-bundle.js +6 -0
- package/dist/web-mod-bundle.js.map +1 -1
- package/dist/web-mod-store.d.ts +5 -0
- package/dist/web-mod-store.js +8 -1
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-prefs.d.ts +8 -0
- package/dist/web-prefs.js +9 -0
- 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-session.d.ts +15 -7
- package/dist/web-session.js +25 -8
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-tools.d.ts +69 -0
- package/dist/web-settings-tools.js +247 -0
- package/dist/web-settings-tools.js.map +1 -0
- package/package.json +1 -1
- package/templates/systemd.service.ejs +8 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// #1918 phase 3: live cross-instance traffic on the org canvas (fleet-canvas.js) — what src/fleet-traffic.ts sends: the
|
|
2
|
+
// stream's `traffic` items, and GET /ui/traffic's snapshot when the tab opens (and on the public link, which has no
|
|
3
|
+
// stream, every POLL_MS while the page is shown — the user's decision 1). Ids are opaque; there is never any text.
|
|
4
|
+
//
|
|
5
|
+
// - The store keeps each message's latest state: queued (a dotted line), delivering (a dash and a pulse), delivered
|
|
6
|
+
// (the line stays FADE_MS while it fades, then goes); consumed, failed, uncertain or cancelled ends it at once (a
|
|
7
|
+
// failed or uncertain delivery is Needs you's to show, #1386). A finished message never comes back.
|
|
8
|
+
// - Edges are routed metro-style through the lanes the layout keeps empty (beside the columns and between card rows
|
|
9
|
+
// inside a box; the bus above each row of teams; the trunk at the left), so a line never crosses a card.
|
|
10
|
+
// - Near: one edge per (from, to, kind, phase), ×N when several share it. Far (zoomed out): one edge per team pair.
|
|
11
|
+
// A broadcast (one message per recipient, sharing an `op`) is a fan-out from its sender; more than BUNDLE_AT
|
|
12
|
+
// recipients, or far, it is one edge per recipient team, ×N (the user's decision 3). At most MAX_EDGES are drawn.
|
|
13
|
+
import { OC } from "./fleet-canvas-layout.js";
|
|
14
|
+
|
|
15
|
+
export const FADE_MS = 3000;
|
|
16
|
+
export const POLL_MS = 5000;
|
|
17
|
+
export const MAX_EDGES = 60;
|
|
18
|
+
export const BUNDLE_AT = 8;
|
|
19
|
+
export const FAR_K = 0.55;
|
|
20
|
+
const MAX_ITEMS = 400, MAX_DONE = 2000;
|
|
21
|
+
const LIVE = new Set(["queued", "delivering"]);
|
|
22
|
+
const ENDS = new Set(["consumed", "failed", "uncertain", "cancelled"]);
|
|
23
|
+
const KINDS = new Set(["task", "report", "query", "update", "send", "broadcast"]);
|
|
24
|
+
const mono = () => (typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now());
|
|
25
|
+
|
|
26
|
+
/** A traffic item as the server sends it, checked; null for anything else. */
|
|
27
|
+
export function itemOk(x) {
|
|
28
|
+
if (!x || typeof x !== "object") return null;
|
|
29
|
+
const { id, from, to, kind, state, op } = x;
|
|
30
|
+
if (typeof id !== "string" || !/^[0-9a-f]{16}$/.test(id) || typeof from !== "string" || !from || typeof to !== "string" || !to) return null;
|
|
31
|
+
if (!KINDS.has(kind) || !(LIVE.has(state) || state === "delivered" || ENDS.has(state))) return null;
|
|
32
|
+
return { id, from, to, kind, state, ...(typeof op === "string" && /^[0-9a-f]{16}$/.test(op) ? { op } : {}) };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The page's traffic: `apply(item, nowMs)` one stream item, `load(snapshot, nowMs)` a snapshot, `sweep(nowMs)` drops
|
|
37
|
+
* faded lines; each returns whether anything shown changed. `items()` what to draw: { …item, fading }.
|
|
38
|
+
*/
|
|
39
|
+
export function createTrafficStore() {
|
|
40
|
+
const items = new Map(); // id → { item, fadeAt }
|
|
41
|
+
const done = new Set(); // ids that ended: a late item for one is ignored
|
|
42
|
+
const finish = (id) => {
|
|
43
|
+
items.delete(id);
|
|
44
|
+
done.add(id);
|
|
45
|
+
if (done.size > MAX_DONE) done.delete(done.values().next().value);
|
|
46
|
+
};
|
|
47
|
+
const store = {
|
|
48
|
+
apply(raw, now = mono()) {
|
|
49
|
+
const it = itemOk(raw);
|
|
50
|
+
if (!it || done.has(it.id)) return false;
|
|
51
|
+
const prev = items.get(it.id);
|
|
52
|
+
if (ENDS.has(it.state)) { const had = !!prev; finish(it.id); return had; }
|
|
53
|
+
if (prev && prev.item.state === "delivered") return false; // delivered never goes back
|
|
54
|
+
if (prev && prev.item.state === it.state) return false;
|
|
55
|
+
items.delete(it.id);
|
|
56
|
+
items.set(it.id, { item: it, fadeAt: it.state === "delivered" ? now + FADE_MS : null });
|
|
57
|
+
while (items.size > MAX_ITEMS) items.delete(items.keys().next().value);
|
|
58
|
+
return true;
|
|
59
|
+
},
|
|
60
|
+
/** A snapshot is the truth for what is in flight: anything live it no longer lists has ended (missed while away). */
|
|
61
|
+
load(snap, now = mono()) {
|
|
62
|
+
const list = snap && Array.isArray(snap.items) ? snap.items.map(itemOk).filter(Boolean) : [];
|
|
63
|
+
const listed = new Set(list.map((i) => i.id));
|
|
64
|
+
let changed = false;
|
|
65
|
+
for (const [id, e] of items) if (!listed.has(id) && LIVE.has(e.item.state)) { finish(id); changed = true; }
|
|
66
|
+
// Delivered, for a line drawn here: it fades now (with no stream, the snapshot is the only news of it). Any other
|
|
67
|
+
// finished message is not drawn (it finished up to two minutes ago), and an ended one goes.
|
|
68
|
+
for (const it of list) {
|
|
69
|
+
if (LIVE.has(it.state) || (it.state === "delivered" && items.has(it.id))) changed = store.apply(it, now) || changed;
|
|
70
|
+
else { const had = items.has(it.id); finish(it.id); changed = changed || had; }
|
|
71
|
+
}
|
|
72
|
+
return changed;
|
|
73
|
+
},
|
|
74
|
+
sweep(now = mono()) {
|
|
75
|
+
let changed = false;
|
|
76
|
+
for (const [id, e] of items) if (e.fadeAt !== null && now >= e.fadeAt) { finish(id); changed = true; }
|
|
77
|
+
return changed;
|
|
78
|
+
},
|
|
79
|
+
items: () => [...items.values()].map((e) => ({ ...e.item, fading: e.fadeAt !== null })),
|
|
80
|
+
fadingCount: () => [...items.values()].filter((e) => e.fadeAt !== null).length,
|
|
81
|
+
};
|
|
82
|
+
return store;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── Where a name is on the canvas ──
|
|
86
|
+
|
|
87
|
+
/** The team a name is drawn in (its first, if it is in two), null for General, undefined when it is not on the canvas. */
|
|
88
|
+
function teamOf(L, name) {
|
|
89
|
+
if (L.tops.some((c) => c.node.name === name)) return null;
|
|
90
|
+
const b = L.boxes.find((x) => x.nodes.some((n) => n.name === name));
|
|
91
|
+
return b ? b : undefined;
|
|
92
|
+
}
|
|
93
|
+
/** An end of a line: General's card, a member's card, or its folded team's box. */
|
|
94
|
+
function endOf(L, name) {
|
|
95
|
+
const top = L.tops.find((c) => c.node.name === name);
|
|
96
|
+
if (top) return { kind: "top", c: top, key: top.key };
|
|
97
|
+
const b = teamOf(L, name);
|
|
98
|
+
if (!b) return null;
|
|
99
|
+
if (b.folded) return { kind: "box", b, key: `box:${b.key}` };
|
|
100
|
+
const i = b.cells.findIndex((c) => c.node.name === name);
|
|
101
|
+
return { kind: "cell", b, i, c: b.cells[i], key: b.cells[i].key };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Routing: lanes the layout keeps empty ──
|
|
105
|
+
|
|
106
|
+
const firstBusOf = (L) => Math.min(...L.boxes.map((b) => b.busY));
|
|
107
|
+
/** From an end up to its row's bus lane: the points, and the lane's y. `o` spreads lines sharing a lane. */
|
|
108
|
+
function outward(L, end, o) {
|
|
109
|
+
if (end.kind === "top") {
|
|
110
|
+
const x = end.c.x + end.c.w / 2 + Math.max(-end.c.w / 2 + 12, Math.min(end.c.w / 2 - 12, o * 2.4));
|
|
111
|
+
const bus = firstBusOf(L) + o;
|
|
112
|
+
return { pts: [{ x, y: end.c.y + end.c.h }, { x, y: bus }], bus };
|
|
113
|
+
}
|
|
114
|
+
const b = end.b;
|
|
115
|
+
if (end.kind === "box") {
|
|
116
|
+
const x = b.x + b.w / 2 + o * 1.5;
|
|
117
|
+
return { pts: [{ x, y: b.y }, { x, y: b.busY + o }], bus: b.busY + o };
|
|
118
|
+
}
|
|
119
|
+
const { PAD, GAP, NW, NH } = OC;
|
|
120
|
+
const card = end.c, col = end.i % b.cols, rowI = Math.floor(end.i / b.cols), lines = Math.ceil(b.cells.length / b.cols);
|
|
121
|
+
const io = Math.max(-PAD / 2 + 3, Math.min(PAD / 2 - 3, o * 0.5));
|
|
122
|
+
const leftLane = b.x + PAD / 2 + io, rightLane = b.x + b.w - PAD / 2 + io;
|
|
123
|
+
let pts;
|
|
124
|
+
if (col === 0 || col === b.cols - 1) {
|
|
125
|
+
const left = col === 0, lane = left ? leftLane : rightLane, cy = card.y + NH / 2 + io;
|
|
126
|
+
pts = [{ x: left ? card.x : card.x + NW, y: cy }, { x: lane, y: cy }, { x: lane, y: b.y }];
|
|
127
|
+
} else {
|
|
128
|
+
// A middle column: down into the gutter below its row, across to the nearer side lane.
|
|
129
|
+
const gy = card.y + NH + (rowI === lines - 1 ? PAD : GAP) / 2 + io / 2, cx = card.x + NW / 2 + o;
|
|
130
|
+
const lane = cx - b.x < b.w / 2 ? leftLane : rightLane;
|
|
131
|
+
pts = [{ x: cx, y: card.y + NH }, { x: cx, y: gy }, { x: lane, y: gy }, { x: lane, y: b.y }];
|
|
132
|
+
}
|
|
133
|
+
const top = pts[pts.length - 1];
|
|
134
|
+
pts.push({ x: top.x, y: b.busY + o });
|
|
135
|
+
return { pts, bus: b.busY + o };
|
|
136
|
+
}
|
|
137
|
+
/** The points of a line from one end to the other: up to the bus, along it (and the trunk, between rows), down. */
|
|
138
|
+
export function route(L, a, z, o) {
|
|
139
|
+
const p = outward(L, a, o), q = outward(L, z, o);
|
|
140
|
+
const back = [...q.pts].reverse();
|
|
141
|
+
if (Math.abs(p.bus - q.bus) < 1) return [...p.pts, ...back];
|
|
142
|
+
const tx = OC.TRUNK - 12 - o;
|
|
143
|
+
return [...p.pts, { x: tx, y: p.bus }, { x: tx, y: q.bus }, ...back];
|
|
144
|
+
}
|
|
145
|
+
/** An orthogonal polyline as SVG path data, its corners rounded. */
|
|
146
|
+
export function pathOf(pts, r = 8) {
|
|
147
|
+
const q = pts.filter((pt, i) => !i || pt.x !== pts[i - 1].x || pt.y !== pts[i - 1].y);
|
|
148
|
+
let d = `M${q[0].x} ${q[0].y}`;
|
|
149
|
+
for (let i = 1; i < q.length - 1; i++) {
|
|
150
|
+
const a = q[i - 1], b = q[i], c = q[i + 1];
|
|
151
|
+
const r1 = Math.min(r, Math.hypot(b.x - a.x, b.y - a.y) / 2, Math.hypot(c.x - b.x, c.y - b.y) / 2);
|
|
152
|
+
const u = { x: Math.sign(a.x - b.x), y: Math.sign(a.y - b.y) }, v = { x: Math.sign(c.x - b.x), y: Math.sign(c.y - b.y) };
|
|
153
|
+
d += ` L${b.x + u.x * r1} ${b.y + u.y * r1} Q${b.x} ${b.y} ${b.x + v.x * r1} ${b.y + v.y * r1}`;
|
|
154
|
+
}
|
|
155
|
+
const last = q[q.length - 1];
|
|
156
|
+
return `${d} L${last.x} ${last.y}`;
|
|
157
|
+
}
|
|
158
|
+
/** Where a line's ×N goes: the middle of its longest straight run. */
|
|
159
|
+
export function badgeAt(pts) {
|
|
160
|
+
let best = pts[0], len = -1;
|
|
161
|
+
for (let i = 1; i < pts.length; i++) {
|
|
162
|
+
const l = Math.hypot(pts[i].x - pts[i - 1].x, pts[i].y - pts[i - 1].y);
|
|
163
|
+
if (l > len) { len = l; best = { x: (pts[i].x + pts[i - 1].x) / 2, y: (pts[i].y + pts[i - 1].y) / 2 }; }
|
|
164
|
+
}
|
|
165
|
+
return best;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── Edges ──
|
|
169
|
+
|
|
170
|
+
/** The colour family of a kind: task, report, broadcast, and the rest (send, query, update). */
|
|
171
|
+
export const hue = (kind) => (kind === "task" || kind === "report" || kind === "broadcast" ? kind : "send");
|
|
172
|
+
const phaseOf = (it) => (it.fading ? "fading" : it.state === "queued" ? "queued" : "live");
|
|
173
|
+
const RANK = { live: 0, queued: 1, fading: 2 };
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The lines to draw from the store's items on layout `L`. `far`: zoomed out (one line per team pair). Each edge:
|
|
177
|
+
* { key, hue, phase, n, from, to, d, pts, badge } — `from`/`to` the instance (or team) names it joins, for search.
|
|
178
|
+
*/
|
|
179
|
+
export function trafficEdges(items, L, { far = false, max = MAX_EDGES } = {}) {
|
|
180
|
+
if (!L.boxes.length && L.tops.length < 2) return [];
|
|
181
|
+
const groups = new Map();
|
|
182
|
+
const add = (key, g) => { const cur = groups.get(key); if (cur) { cur.n += g.n; cur.names.push(...g.names); } else groups.set(key, g); };
|
|
183
|
+
const teamKey = (name) => { const t = teamOf(L, name); return t === null ? "\u0000general" : t ? t.key : undefined; };
|
|
184
|
+
const teamEnd = (name) => { const t = teamOf(L, name); return t === null ? endOf(L, name) : t ? { kind: "box", b: t, key: `box:${t.key}` } : null; };
|
|
185
|
+
// Broadcasts: one fan-out per op; big ones (or far) bundle per recipient team.
|
|
186
|
+
const byOp = new Map();
|
|
187
|
+
for (const it of items) if (it.kind === "broadcast" && it.op) { const l = byOp.get(it.op) || []; l.push(it); byOp.set(it.op, l); }
|
|
188
|
+
const bundled = new Set();
|
|
189
|
+
for (const list of byOp.values()) if (far || list.length > BUNDLE_AT) for (const it of list) bundled.add(it.id);
|
|
190
|
+
for (const it of items) {
|
|
191
|
+
const phase = phaseOf(it), names = [it.from, it.to];
|
|
192
|
+
if (far || bundled.has(it.id)) {
|
|
193
|
+
const ta = teamKey(it.from), tb = teamKey(it.to);
|
|
194
|
+
if (ta === tb) continue; // inside one team: not drawn far away
|
|
195
|
+
const k = far ? "team" : "broadcast";
|
|
196
|
+
const a = far ? teamEnd(it.from) : endOf(L, it.from), z = teamEnd(it.to);
|
|
197
|
+
if (!a || !z) continue;
|
|
198
|
+
add(`${k}|${a.key}>${z.key}|${phase === "fading" ? "fading" : "live"}`, { a, z, hue: k === "team" ? "team" : "broadcast", phase: phase === "fading" ? "fading" : phase, n: 1, names });
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const a = endOf(L, it.from), z = endOf(L, it.to);
|
|
202
|
+
if (!a || !z || a.key === z.key) continue; // both in one folded team
|
|
203
|
+
add(`${a.key}>${z.key}|${hue(it.kind)}|${phase}`, { a, z, hue: hue(it.kind), phase, n: 1, names });
|
|
204
|
+
}
|
|
205
|
+
const list = [...groups.entries()].sort((x, y) => RANK[x[1].phase] - RANK[y[1].phase]).slice(0, max);
|
|
206
|
+
return list.map(([key, g], i) => {
|
|
207
|
+
const o = ((i % 7) - 3) * 7; // neighbours 7 px apart (within the ±22 px bus lane)
|
|
208
|
+
const pts = route(L, g.a, g.z, o);
|
|
209
|
+
return { key, hue: g.hue, phase: g.phase, n: g.n, names: [...new Set(g.names)], d: pathOf(pts), pts, badge: g.n > 1 ? badgeAt(pts) : null };
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** For the list: what each instance is sending now, in words ("task → quill"), live messages only. */
|
|
214
|
+
export function sendingBy(items) {
|
|
215
|
+
const out = new Map();
|
|
216
|
+
for (const it of items) {
|
|
217
|
+
if (it.fading) continue;
|
|
218
|
+
const l = out.get(it.from) || [];
|
|
219
|
+
const key = `${it.kind}>${it.to}>${it.state}`;
|
|
220
|
+
if (!l.some((x) => x.key === key)) l.push({ key, kind: it.kind, to: it.to, queued: it.state === "queued" });
|
|
221
|
+
out.set(it.from, l);
|
|
222
|
+
}
|
|
223
|
+
return out;
|
|
224
|
+
}
|
package/dist/ui/panel-chat.js
CHANGED
|
@@ -10,7 +10,7 @@ import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-
|
|
|
10
10
|
import { t } from "/assets/app-i18n.js";
|
|
11
11
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
12
12
|
import { useLease } from "/assets/app-ctx.js";
|
|
13
|
-
import { namesOf, namesStore } from "/assets/instance-nav.js";
|
|
13
|
+
import { modelText, namesOf, namesStore } from "/assets/instance-nav.js";
|
|
14
14
|
import { PanelHeader, onPanelKey, setTitle, statusClass, statusLabel, openDrawer, requestNewInstance } from "/assets/app-shell.js";
|
|
15
15
|
import { detailsPath, textLinkSegments } from "/assets/app-route.js";
|
|
16
16
|
import { InstanceSwitch } from "/assets/instance-switch.js";
|
|
@@ -281,7 +281,7 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
281
281
|
// #1269 quick actions: the model and effort it runs, each a way to /model or /effort (the same pick list).
|
|
282
282
|
const pick = (command) => store.runCommand(name, command, "", { alive: () => lease.current() });
|
|
283
283
|
const sub = html`<span class="status"><span class=${`dot ${cls}`} aria-hidden="true"></span>${statusLabel(inst, exec, awaiting)}</span>
|
|
284
|
-
${inst.model ? html`<button type="button" class="hd-chip" title=${t("chat.chipModel", inst
|
|
284
|
+
${inst.model ? html`<button type="button" class="hd-chip" title=${t("chat.chipModel", modelText(inst))} aria-label=${t("chat.chipModel", modelText(inst))} onClick=${() => pick("model")}>${modelText(inst)}</button>` : null}
|
|
285
285
|
${inst.effort ? html`<button type="button" class="hd-chip" title=${t("chat.chipEffort", inst.effort)} aria-label=${t("chat.chipEffort", inst.effort)} onClick=${() => pick("effort")}>${inst.effort}</button>` : null}`;
|
|
286
286
|
return html`<div class=${`panel p-chat${wrap ? " wrap-code" : ""}`} ref=${view}>
|
|
287
287
|
<${PanelHeader} title=${headerName(namesOf(inst || { name }, names.primary))} sub=${sub} nav=${html`<${InstanceSwitch} name=${name} current="chat" />`}><${ChatEffectPosters} name=${name} lease=${lease} element=${effectSurface} /><${ChatEffects} name=${name} lease=${lease} element=${effectSurface} store=${store} open=${dialog === "effects"} onClose=${() => setDialog(null)} /><${HeaderTools} navKey=${navKey} /><${Menu} items=${items} label=${t("app.more")} /></${PanelHeader}>
|
|
@@ -312,6 +312,12 @@ function Thread({ name, th }) {
|
|
|
312
312
|
const thread = createThread(list.current, scroller.current, {
|
|
313
313
|
t, tf: t, isUser: (x) => store.isUser(x, name), onJump: setJump, onEmpty: setEmpty,
|
|
314
314
|
setPreviewOptIn, copyText, download: downloadHtml, toggleWrap,
|
|
315
|
+
effectTrialStatus: x => getAppEffects()?.trialStatus(x) ?? "localAdmin",
|
|
316
|
+
fixEffectTrial: (x, reason) => {
|
|
317
|
+
if (reason === "busy") getAppEffects()?.stopTrial();
|
|
318
|
+
else if (reason === "localAdmin") location.href = "/signin?next=" + encodeURIComponent(location.pathname + location.search);
|
|
319
|
+
else navigate("/ui/settings?section=appearance");
|
|
320
|
+
},
|
|
315
321
|
canTryEffect: x => getAppEffects()?.canTry(x) === true,
|
|
316
322
|
startEffectTrial: x => { const seq = navStore.get().seq; void getAppEffects()?.startTrial(x).then(ok => { if (!ok && live && navStore.get().seq === seq && document.hasFocus?.() === true && document.visibilityState === "visible") toast(t("chat.effectTrialUnavailable")); }); },
|
|
317
323
|
clickReplyButton: (id, index) => store.clickReplyButton(name, id, index), // #1266
|
package/dist/ui/panel-details.js
CHANGED
|
@@ -14,6 +14,7 @@ import { PanelHeader, setTitle, statusClass, statusLabel, openDrawer } from "/as
|
|
|
14
14
|
import { navigate } from "/assets/app-nav.js";
|
|
15
15
|
import { settingsPath } from "/assets/app-route.js";
|
|
16
16
|
import { InstanceSwitch } from "/assets/instance-switch.js";
|
|
17
|
+
import { modelText } from "/assets/instance-nav.js";
|
|
17
18
|
import { HeaderTools } from "/assets/header-tools.js";
|
|
18
19
|
import { Menu } from "/assets/ui-menu.js";
|
|
19
20
|
import { themeMenuItems, themesStore } from "/assets/app-themes.js";
|
|
@@ -143,7 +144,7 @@ function DetailsView({ name, inst, navKey, exec, awaiting }) {
|
|
|
143
144
|
<button type="button" class="btn btn-sm d-edit-settings" onClick=${editInSettings}>${t("chat.dEditInSettings")}</button></div></section>
|
|
144
145
|
<section class="card"><h3>${t("chat.dRuntime")}</h3>
|
|
145
146
|
${row(t("chat.dBackend"), d.backend || "--")}
|
|
146
|
-
${row(t("chat.dModel"), `${d.model || sl.model?.display_name || "--"}${d.model_source === "live" ? "" : src(d.model_source)}`)}
|
|
147
|
+
${row(t("chat.dModel"), `${(d.model && modelText(d)) || sl.model?.display_name || "--"}${d.model_source === "live" ? "" : src(d.model_source)}`)}
|
|
147
148
|
${d.effort ? row(t("chat.dEffort"), `${d.effort}${src(d.effort_source)}`) : null}
|
|
148
149
|
${row(t("chat.dCost"), `$${cost.toFixed(2)} ${t("chat.dCostNote")}`)}
|
|
149
150
|
${row(t("chat.dContext"), html`<span class="pct">${ctx != null ? `${Math.round(ctx)}%` : "--"}</span>${ctx != null ? bar(ctx, ctx > 80 ? "warn" : "") : null}`)}
|
package/dist/ui/panel-fleet.js
CHANGED
|
@@ -18,14 +18,23 @@ import { Icon } from "/assets/ui-icons.js";
|
|
|
18
18
|
import { toast } from "/assets/ui-toast.js";
|
|
19
19
|
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
20
20
|
import { confirmedWrite } from "./settings-confirm.js";
|
|
21
|
-
import {
|
|
21
|
+
import { OrgCanvas } from "./fleet-canvas.js";
|
|
22
22
|
import { labelOf } from "/assets/instance-nav.js";
|
|
23
23
|
import { CacheReport } from "./fleet-cache.js";
|
|
24
24
|
|
|
25
|
+
/** #2015: one of the largest processes as text: "python3 18.1 GB (dev-a)". */
|
|
26
|
+
export function memoryHogText(p) {
|
|
27
|
+
const size = p.rssBytes >= 1024 ** 3 ? `${(p.rssBytes / 1024 ** 3).toFixed(1)} GB` : `${Math.round(p.rssBytes / 1024 ** 2)} MB`;
|
|
28
|
+
const owner = p.owner === "instance" && p.instance ? p.instance
|
|
29
|
+
: t(p.owner === "fleet" ? "fleet.hostMemory_ownerFleet" : p.owner === "tmux" ? "fleet.hostMemory_ownerTmux" : "fleet.hostMemory_ownerOther");
|
|
30
|
+
return `${p.name} ${size} (${owner})`;
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
register("fleet", {
|
|
26
34
|
en: {
|
|
27
35
|
hostMemory_elevated: "Host memory pressure: elevated — new agents start one at a time, at least 5 s apart",
|
|
28
36
|
hostMemory_critical: "Host memory pressure: critical — new agents wait for memory to recover",
|
|
37
|
+
hostMemory_top: "Largest: {0}", hostMemory_ownerFleet: "the fleet", hostMemory_ownerTmux: "tmux", hostMemory_ownerOther: "not an instance",
|
|
29
38
|
title: "Fleet", tasks: "Tasks", schedules: "Schedules", teams: "Teams", org: "Org chart", cache: "Cache", config: "Config",
|
|
30
39
|
docs: "Docs", docsNewTab: "Docs (opens in a new tab)",
|
|
31
40
|
newTask: "New task", newSchedule: "New schedule", newTeam: "New team",
|
|
@@ -57,6 +66,7 @@ register("fleet", {
|
|
|
57
66
|
"zh-TW": {
|
|
58
67
|
hostMemory_elevated: "主機記憶體壓力偏高:新的 agent 一次啟動一個,至少間隔 5 秒",
|
|
59
68
|
hostMemory_critical: "主機記憶體壓力嚴重:新的 agent 會等記憶體恢復再啟動",
|
|
69
|
+
hostMemory_top: "最大:{0}", hostMemory_ownerFleet: "fleet 本身", hostMemory_ownerTmux: "tmux", hostMemory_ownerOther: "不屬於任何 instance",
|
|
60
70
|
title: "Fleet", tasks: "Tasks", schedules: "排程", teams: "Teams", org: "組織圖", cache: "快取", config: "設定",
|
|
61
71
|
docs: "文件", docsNewTab: "文件(在新分頁開啟)",
|
|
62
72
|
newTask: "新增 task", newSchedule: "新增排程", newTeam: "新增 team",
|
|
@@ -132,8 +142,10 @@ export function FleetPanel({ route, navKey }) {
|
|
|
132
142
|
useEffect(() => { setTitle(`${t("fleet.title")} · ${t(`fleet.${tab}`)}`); }, [tab, navKey]);
|
|
133
143
|
const Body = { tasks: Tasks, schedules: Schedules, teams: Teams, org: Org, cache: Cache, config: Config }[tab] || Tasks;
|
|
134
144
|
// #1685: host memory pressure slowing or holding new agents — shown here, where macOS's routine "elevated" is not a chat notice.
|
|
135
|
-
const { hostMemory } = useStore(appStore);
|
|
136
|
-
|
|
145
|
+
const { hostMemory, hostMemoryTop } = useStore(appStore);
|
|
146
|
+
// #2015: and which processes hold the most, and whose — text only (never chat, #1712).
|
|
147
|
+
const top = hostMemory && hostMemoryTop?.length ? t("fleet.hostMemory_top", hostMemoryTop.map(memoryHogText).join(" · ")) : "";
|
|
148
|
+
const sub = hostMemory ? html`<span class=${`status host-mem ${hostMemory}`} role="status"><span class=${`dot ${hostMemory === "critical" ? "bad" : "warn"}`} aria-hidden="true"></span>${t(`fleet.hostMemory_${hostMemory}`)}${top ? html`<span class="host-mem-top">${top}</span>` : null}</span>` : null;
|
|
137
149
|
return html`<div data-effect-panel="fleet" class="panel p-fleet">
|
|
138
150
|
<${PanelHeader} title=${t("fleet.title")} sub=${sub}><${DocsLink} /><${HeaderTools} navKey=${navKey} /></${PanelHeader}>
|
|
139
151
|
<nav data-trust="panel-navigation" class="seg" aria-label=${t("fleet.title")}>${FLEET_TABS.map(k => html`<a key=${k} href=${fleetPath(k)} class=${`seg-item${k === tab ? " active" : ""}`}
|
|
@@ -255,11 +267,11 @@ function Teams({ lease }) {
|
|
|
255
267
|
${editing ? html`<${CreateTeam} team=${editing} onClose=${() => setEditing(null)} onDone=${() => setVersion(v => v + 1)} />` : null}`;
|
|
256
268
|
}
|
|
257
269
|
|
|
258
|
-
/** #1389: the org chart — its structure read once per opening, its live state from the app store (fleet-
|
|
270
|
+
/** #1389: the org chart — its structure read once per opening, its live state from the app store. #1918: a canvas, or the list (fleet-canvas.js). */
|
|
259
271
|
function Org({ lease }) {
|
|
260
272
|
const state = useLoad(lease, "/ui/org", 0);
|
|
261
273
|
const app = useStore(appStore);
|
|
262
|
-
return html`<${Loaded} state=${state}>${(org) => html`<${
|
|
274
|
+
return html`<${Loaded} state=${state}>${(org) => html`<${OrgCanvas} org=${org} app=${app} lease=${lease} />`}</${Loaded}>`;
|
|
263
275
|
}
|
|
264
276
|
|
|
265
277
|
/** #1468: the prompt-cache analysis — one read per opening and per window chosen; Refresh reads again (fleet-cache.js). */
|
|
@@ -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 { groupLabel, 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";
|
|
@@ -87,7 +88,7 @@ async function loadLive(lease) {
|
|
|
87
88
|
if (!lease.current()) return null;
|
|
88
89
|
const live = {}, classicLive = {};
|
|
89
90
|
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 };
|
|
91
|
+
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
92
|
if (i.classic) classicLive[i.name] = { status: i.status, state: i.state };
|
|
92
93
|
}
|
|
93
94
|
// /api/fleet carries live state for ClassicBot rooms; the profiles are the fallback for one not listed there yet.
|
|
@@ -473,7 +474,7 @@ function Agents({ ctx, search, openDialog }) {
|
|
|
473
474
|
return html`<section key=${group} class="s-group">
|
|
474
475
|
<button type="button" class="s-group-head" aria-expanded=${folded ? "false" : "true"}
|
|
475
476
|
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>
|
|
477
|
+
<${Icon} name="chevron" size=${14} cls=${folded ? "caret folded" : "caret"} /><span class="grow">${groupLabel(group)}</span><span class="count">${names.length}</span></button>
|
|
477
478
|
${folded ? null : names.map((name) => {
|
|
478
479
|
const inst = insts[name];
|
|
479
480
|
const st = (ctx.live[name] && ctx.live[name].status) || "stopped";
|
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) : "";
|
|
@@ -15,7 +15,8 @@ import { AgentWidget, checkWidget, fleetBinds, setSidebarWidget, sidebarWidgetRa
|
|
|
15
15
|
import { confirmedWrite } from "./settings-confirm.js";
|
|
16
16
|
import { savePref } from "/assets/app-prefs.js";
|
|
17
17
|
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
18
|
-
import { colorsOff, defaultPlaces, PLACEMENTS, setColorsOff, setDefaultPlaces, useIdentityColors } from "/assets/identity-color.js";
|
|
18
|
+
import { colorsOff, defaultPlaces, identityClasses, identityOf, PLACEMENTS, paintIdentity, setColorsOff, setDefaultPlaces, useIdentityColors } from "/assets/identity-color.js";
|
|
19
|
+
import { CardTitle } from "./settings-learn.js";
|
|
19
20
|
import "./settings-strings.js";
|
|
20
21
|
|
|
21
22
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
@@ -42,22 +43,38 @@ function refusal(body) {
|
|
|
42
43
|
export function AppearanceSection() {
|
|
43
44
|
const publicLink = typeof document !== "undefined" && document.body && document.body.dataset.publicLink === "1";
|
|
44
45
|
if (publicLink) return html`<section class="card s-appearance"><h3>${tn("cssTitle")}</h3><p class="sub">${tn("cssPublicLink")}</p></section>`;
|
|
45
|
-
|
|
46
|
+
const group = (key) => html`<h2 class="s-ap-group">${tn(key)}</h2>`;
|
|
47
|
+
return html`${group("groupLook")}<${ThemesCard} /><${IdentityColorCard} /><${CardSizeCard} />
|
|
48
|
+
${group("groupLayout")}<${SidebarWidgetCard} />
|
|
49
|
+
${group("groupCss")}<${OwnerCssCard} /><${DeviceCard} />
|
|
50
|
+
${group("groupMods")}<${ModsCard} /><${EffectsCard} />
|
|
51
|
+
${group("groupAdvanced")}<${CardLimitsCard} /><${RestoreDefaultsCard} />`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** #2056 B: a sample sidebar row in the host's default colour places, so the choice is seen rather than described. */
|
|
55
|
+
const SAMPLE = { color: { person: "preset:teal" } };
|
|
56
|
+
function ColorSample({ places, off }) {
|
|
57
|
+
const id = identityOf(SAMPLE, { hostDefault: places, off });
|
|
58
|
+
return html`<div class="idc-preview s-colors-sample" aria-hidden="true"><span class=${`inst v-inst ${identityClasses(id)}`} ref=${(el) => paintIdentity(el, id)}>
|
|
59
|
+
<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
60
|
}
|
|
47
61
|
|
|
48
62
|
/**
|
|
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.
|
|
63
|
+
* #1958: identity colours on this host — where they show for an instance nobody chose for (#2056: name, bar and
|
|
64
|
+
* ring unless changed), and a switch that turns them off everywhere while every pick is kept.
|
|
51
65
|
*/
|
|
52
66
|
function IdentityColorCard() {
|
|
53
67
|
useIdentityColors();
|
|
54
68
|
const places = defaultPlaces(), off = colorsOff();
|
|
55
69
|
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"
|
|
70
|
+
return html`<section class="card s-appearance s-synced s-colors"><${CardTitle} title=${tn("colorTitle")} topic="colors" />
|
|
57
71
|
<p class="sub">${tn("colorLead")}</p>
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
72
|
+
<div class="s-colors-row">
|
|
73
|
+
<fieldset class="idc-places" disabled=${off}><legend>${tn("colorPlaces")}</legend>
|
|
74
|
+
${PLACEMENTS.map((p) => html`<label key=${p} class="check"><input type="checkbox" checked=${places.includes(p)} onChange=${() => toggle(p)} /> ${t(`color.place_${p}`)}</label>`)}
|
|
75
|
+
</fieldset>
|
|
76
|
+
<${ColorSample} places=${places} off=${off} />
|
|
77
|
+
</div>
|
|
61
78
|
<label class="check"><input type="checkbox" class="s-colors-off" checked=${off} onChange=${(e) => setColorsOff(e.target.checked)} /> <span>${tn("colorOff")}</span></label>
|
|
62
79
|
<p class="sub">${tn("colorOffLead")}</p>
|
|
63
80
|
</section>`;
|
|
@@ -88,7 +105,7 @@ function RestoreDefaultsCard() {
|
|
|
88
105
|
setBusy(false);
|
|
89
106
|
if (r.ok) toast(tn("restoreDone")); else toast(tn("restoreCssFailed", r.error), false);
|
|
90
107
|
}
|
|
91
|
-
return html`<section class="card s-appearance s-restore"
|
|
108
|
+
return html`<section class="card s-appearance s-restore"><${CardTitle} title=${tn("restoreTitle")} topic="restore" />
|
|
92
109
|
<p class="sub">${tn("restoreLead")}</p>
|
|
93
110
|
<button type="button" class="btn" disabled=${busy} onClick=${go}>${tn("restoreTitle")}</button>
|
|
94
111
|
</section>`;
|
|
@@ -98,7 +115,7 @@ function RestoreDefaultsCard() {
|
|
|
98
115
|
function CardSizeCard() {
|
|
99
116
|
useStore(cardSizeStore);
|
|
100
117
|
const size = cardSizeDefault();
|
|
101
|
-
return html`<section class="card s-appearance s-synced"
|
|
118
|
+
return html`<section class="card s-appearance s-synced"><${CardTitle} title=${tn("cardSizeTitle")} topic="cards" />
|
|
102
119
|
<div class="card-size" role="group" aria-label=${tn("cardSizeTitle")}>
|
|
103
120
|
${CARD_SIZES.map((s) => html`<button key=${s} type="button" class=${`seg-item${s === size ? " active" : ""}`} aria-pressed=${s === size ? "true" : "false"}
|
|
104
121
|
onClick=${() => setCardSizeDefault(s)}>${t(`card.size${s.toUpperCase()}Name`)}</button>`)}
|
|
@@ -131,7 +148,7 @@ function CardLimitsCard() {
|
|
|
131
148
|
};
|
|
132
149
|
useEffect(() => { load(); }, []);
|
|
133
150
|
if (!view) return null;
|
|
134
|
-
if (view.error) return html`<section class="card s-appearance"
|
|
151
|
+
if (view.error) return html`<section class="card s-appearance"><${CardTitle} title=${tn("cardLimitsTitle")} topic="limits" /><p class="sub">${tn("cardLimitsUnavailable")}</p></section>`;
|
|
135
152
|
const configured = (g, k) => (view.configured && view.configured[g] && typeof view.configured[g][k] === "number" ? view.configured[g][k] : null);
|
|
136
153
|
const save = async () => {
|
|
137
154
|
const body = {};
|
|
@@ -148,7 +165,7 @@ function CardLimitsCard() {
|
|
|
148
165
|
if (r.ok) { setMsg(tn("cardLimitsSaved")); load(); } else setMsg(tn("cardLimitsFailed", (r.body && r.body.error) || r.status));
|
|
149
166
|
};
|
|
150
167
|
const warnings = Array.isArray(view.warnings) ? view.warnings : [];
|
|
151
|
-
return html`<section class="card s-appearance"
|
|
168
|
+
return html`<section class="card s-appearance"><${CardTitle} title=${tn("cardLimitsTitle")} topic="limits" />
|
|
152
169
|
<p class="sub">${tn("cardLimitsLead")}</p>
|
|
153
170
|
<fieldset class="form" disabled=${busy}>
|
|
154
171
|
${["portrait", "banner", "instance_quota"].map((g) => html`<div key=${g} class="grid3">
|
|
@@ -170,7 +187,7 @@ function CardLimitsCard() {
|
|
|
170
187
|
/** #1629 r2 (d): whether this person's screens show agents' themes (and, as they come, effects) — synced. */
|
|
171
188
|
function ThemesCard() {
|
|
172
189
|
const s = useStore(themesStore);
|
|
173
|
-
return html`<section class="card s-appearance s-synced"
|
|
190
|
+
return html`<section class="card s-appearance s-synced"><${CardTitle} title=${tn("themesTitle")} topic="themes" />
|
|
174
191
|
<label class="check"><input type="checkbox" checked=${s.shown} disabled=${safeMode()} onChange=${(e) => setThemesShown(e.target.checked)} />
|
|
175
192
|
<span>${tn("themesShow")}</span></label>
|
|
176
193
|
<p class="sub">${safeMode() ? tn("themesSafeMode") : tn("themesShowLead")}</p>
|
|
@@ -209,7 +226,7 @@ function SidebarWidgetCard() {
|
|
|
209
226
|
toast(tn("widgetSaved"));
|
|
210
227
|
};
|
|
211
228
|
const remove = () => { setSidebarWidget(null); setText(""); setError(""); toast(tn("widgetRemoved")); };
|
|
212
|
-
return html`<section class="card s-appearance s-synced s-widget"
|
|
229
|
+
return html`<section class="card s-appearance s-synced s-widget"><${CardTitle} title=${tn("widgetTitle")} topic="widgets" />
|
|
213
230
|
<p class="sub">${tn("widgetLead")}</p>
|
|
214
231
|
<label class="field"><span class="sr-only">${tn("widgetTitle")}</span>
|
|
215
232
|
<textarea class="s-widget-text" rows="10" spellcheck=${false} autocapitalize="off" autocomplete="off" value=${text}
|
|
@@ -240,8 +257,8 @@ function OwnerCssCard() {
|
|
|
240
257
|
useEffect(() => { load(); }, []);
|
|
241
258
|
// A trial answered (kept or reverted) changes what is in force: read it again, keeping the editor's text.
|
|
242
259
|
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"
|
|
260
|
+
if (!state) return html`<section class="card s-appearance"><${CardTitle} title=${tn("cssTitle")} topic="css" /><p class="sub">…</p></section>`;
|
|
261
|
+
if (state.failed) return html`<section class="card s-appearance"><${CardTitle} title=${tn("cssTitle")} topic="css" /><p class="err">${tn("cssLoadFailed")}</p></section>`;
|
|
245
262
|
|
|
246
263
|
const save = async () => {
|
|
247
264
|
setBusy(true); setError("");
|
|
@@ -285,7 +302,7 @@ function OwnerCssCard() {
|
|
|
285
302
|
? (state.pending ? tn("cssOnTrial", short(state.active)) : tn("cssInForce", short(state.active)))
|
|
286
303
|
: tn("cssNone");
|
|
287
304
|
const assetBytes = state.assets.reduce((n, a) => n + a.bytes, 0);
|
|
288
|
-
return html`<section class="card s-appearance s-owner-css"
|
|
305
|
+
return html`<section class="card s-appearance s-owner-css"><${CardTitle} title=${tn("cssTitle")} topic="css" />
|
|
289
306
|
<p class="sub">${tn("cssLead")}</p>
|
|
290
307
|
<p class="s-css-status" role="status">${inForce}</p>
|
|
291
308
|
<label class="field"><span class="sr-only">${tn("cssTitle")}</span>
|
|
@@ -319,7 +336,6 @@ function DeviceCard() {
|
|
|
319
336
|
return html`<section class="card s-appearance s-local"><h3>${tn("cssDeviceTitle")}</h3>
|
|
320
337
|
<label class="check"><input type="checkbox" checked=${off} onChange=${(e) => { setOwnerCssOffHere(e.target.checked); setOff(ownerCssOffHere()); }} />
|
|
321
338
|
<span>${tn("cssDeviceOff")}</span></label>
|
|
322
|
-
<p class="sub">${tn("cssDeviceOffLead")}</p>
|
|
323
339
|
<p class="sub">${tn("cssSafe")}</p>
|
|
324
340
|
</section>`;
|
|
325
341
|
}
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
ACCESS_MODES, AGENT_FIELDS, AGENT_MODES, BOT_FIELDS, CH_TYPES, CLASSIC_FIELDS, HANG_TIMEOUT_DEFAULT_MINUTES, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS,
|
|
18
18
|
VISIBILITY_MODES, accessConfirmations, agentForm, agentPatch, backendOptions, changedFields, channelId, channelsOf,
|
|
19
19
|
chLabel, hasOwn, impactOf, nonNegative, nonNegativeInteger, positive, replyGuardSupported, sameValue, visibilityDefault,
|
|
20
|
-
|
|
20
|
+
classicWebEchoEffective, worstImpact,
|
|
21
21
|
} from "./settings-model.js";
|
|
22
22
|
|
|
23
23
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
@@ -557,7 +557,7 @@ export function ClassicDialog({ room, ctx, onClose }) {
|
|
|
557
557
|
backend: c.backend || inherited.backend, model: c.model || "",
|
|
558
558
|
auto_pause_after: String(c.auto_pause_after ?? inherited.auto_pause_after), tool_progress: c.tool_progress ?? inherited.tool_progress,
|
|
559
559
|
reply_completion_guard: c.reply_completion_guard ?? inherited.reply_completion_guard,
|
|
560
|
-
collab: !!c.collab, web_echo:
|
|
560
|
+
collab: !!c.collab, web_echo: classicWebEchoEffective(c, fleet), context_lines: String(c.context_lines ?? cd.context_lines ?? 5),
|
|
561
561
|
inherit: { auto_pause_after: !hasOwn(c, "auto_pause_after"), tool_progress: !hasOwn(c, "tool_progress"), reply_completion_guard: !hasOwn(c, "reply_completion_guard") },
|
|
562
562
|
}));
|
|
563
563
|
const set = (k) => (v) => setF((x) => ({ ...x, [k]: v }));
|
|
@@ -580,12 +580,9 @@ export function ClassicDialog({ room, ctx, onClose }) {
|
|
|
580
580
|
auto_pause_after: hasOwn(c, "auto_pause_after") ? c.auto_pause_after : null,
|
|
581
581
|
tool_progress: hasOwn(c, "tool_progress") ? c.tool_progress : null,
|
|
582
582
|
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:
|
|
583
|
+
collab: !!c.collab, context_lines: c.context_lines ?? cd.context_lines ?? 5, web_echo: classicWebEchoEffective(c, fleet),
|
|
584
584
|
});
|
|
585
585
|
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
586
|
if (!lease.current()) return;
|
|
590
587
|
ctx.stageClassic(c, patch);
|
|
591
588
|
onClose();
|
|
@@ -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}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// #2056 B: a card title with a small "?" that opens the card's section of the docs, so Settings → Web appearance
|
|
2
|
+
// keeps one short line per control and the long explanations live in docs/web-dashboard(.zh-TW).md.
|
|
3
|
+
import { html } from "/assets/app-html.js";
|
|
4
|
+
import { lang, t } from "/assets/app-i18n.js";
|
|
5
|
+
import "./settings-strings.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* #2056 B: where each card's long explanation lives — a heading of docs/web-dashboard(.zh-TW).md on GitHub, so the
|
|
9
|
+
* page keeps one short line per control and a small "?" for the rest.
|
|
10
|
+
*/
|
|
11
|
+
const DOCS = { en: "https://github.com/songsid/AgEnD/blob/main/docs/web-dashboard.md", "zh-TW": "https://github.com/songsid/AgEnD/blob/main/docs/web-dashboard.zh-TW.md" };
|
|
12
|
+
export const LEARN_ANCHORS = {
|
|
13
|
+
colors: ["identity-colours", "識別色"], css: ["custom-css-settings--web-appearance", "自訂-css設定--網頁外觀"], themes: ["agents-themes", "agent-的主題"],
|
|
14
|
+
cards: ["identity-cards", "身分卡"], limits: ["card-upload-limits", "身分卡上傳限制"], widgets: ["widgets", "小工具"], mods: ["mods", "mod"],
|
|
15
|
+
effects: ["effects", "效果"], restore: ["restore-defaults", "還原預設值"],
|
|
16
|
+
};
|
|
17
|
+
export function learnHref(topic, language = lang()) {
|
|
18
|
+
const zh = language === "zh-TW";
|
|
19
|
+
return `${DOCS[zh ? "zh-TW" : "en"]}#${encodeURIComponent(LEARN_ANCHORS[topic][zh ? 1 : 0])}`;
|
|
20
|
+
}
|
|
21
|
+
/** A card title with its "?" link to the docs (a new tab; the page itself never fetches anything). */
|
|
22
|
+
export function CardTitle({ title, topic }) {
|
|
23
|
+
return html`<h3 class="s-ap-title"><span>${title}</span>${topic ? html`<a class="s-learn" href=${learnHref(topic)} target="_blank" rel="noopener noreferrer"
|
|
24
|
+
aria-label=${t("settings.learnMore", title)} title=${t("settings.learnMore", title)}>?</a>` : null}</h3>`;
|
|
25
|
+
}
|