@songsid/agend 2.1.4-beta.18 → 2.1.4-beta.19
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/backend/antigravity.d.ts +1 -0
- package/dist/backend/antigravity.js +4 -0
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/codex.d.ts +17 -0
- package/dist/backend/codex.js +51 -0
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/types.d.ts +10 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.js +48 -21
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.js +5 -3
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/types.d.ts +1 -1
- package/dist/cli.js +31 -21
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +3 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +5 -1
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +33 -0
- package/dist/daemon.js +134 -6
- package/dist/daemon.js.map +1 -1
- package/dist/doctor.js +34 -27
- package/dist/doctor.js.map +1 -1
- package/dist/fleet-context.d.ts +2 -0
- package/dist/fleet-manager.d.ts +14 -0
- package/dist/fleet-manager.js +305 -75
- package/dist/fleet-manager.js.map +1 -1
- package/dist/locale.js +656 -330
- package/dist/locale.js.map +1 -1
- package/dist/outbound-handlers.js +5 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/restart-progress.d.ts +2 -0
- package/dist/restart-progress.js +3 -0
- package/dist/restart-progress.js.map +1 -1
- package/dist/scheduler/db.d.ts +5 -0
- package/dist/scheduler/db.js +35 -0
- package/dist/scheduler/db.js.map +1 -1
- package/dist/tips.d.ts +34 -0
- package/dist/tips.js +554 -0
- package/dist/tips.js.map +1 -0
- package/dist/topic-commands.d.ts +5 -2
- package/dist/topic-commands.js +101 -61
- package/dist/topic-commands.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/ui/dashboard.html +55 -32
- package/dist/ui/settings.html +46 -37
- package/dist/ui/view.html +23 -15
- package/package.json +1 -1
package/dist/ui/settings.html
CHANGED
|
@@ -127,15 +127,15 @@
|
|
|
127
127
|
<body>
|
|
128
128
|
<header>
|
|
129
129
|
<h1 data-i18n="title">⚙️ AgEnD Settings</h1>
|
|
130
|
-
<span id="verBadge" title="What's New" style="font-size:12px;color:var(--muted);cursor:pointer;padding:2px 8px;border:1px solid var(--border);border-radius:12px">v…</span>
|
|
130
|
+
<span id="verBadge" data-i18n-title="whatsNew" title="What's New" style="font-size:12px;color:var(--muted);cursor:pointer;padding:2px 8px;border:1px solid var(--border);border-radius:12px">v…</span>
|
|
131
131
|
<div class="grow"></div>
|
|
132
|
-
<button class="help-btn" id="langBtn" title="Language">🌐</button>
|
|
133
|
-
<button class="help-btn" id="helpBtn" title="Help">ℹ️</button>
|
|
134
|
-
<button id="advancedToggle" title="Show additional controls">Show advanced</button>
|
|
132
|
+
<button class="help-btn" id="langBtn" data-i18n-title="languageButton" title="Language">🌐</button>
|
|
133
|
+
<button class="help-btn" id="helpBtn" data-i18n-title="helpButton" title="Help">ℹ️</button>
|
|
134
|
+
<button id="advancedToggle" data-i18n-title="showAdditional" title="Show additional controls">Show advanced</button>
|
|
135
135
|
</header>
|
|
136
136
|
<div class="help-backdrop" id="helpBackdrop"></div>
|
|
137
137
|
<aside class="help-panel" id="helpPanel">
|
|
138
|
-
<button class="close" id="helpClose" title="Close">×</button>
|
|
138
|
+
<button class="close" id="helpClose" data-i18n-title="close" title="Close">×</button>
|
|
139
139
|
<div id="helpContent"></div>
|
|
140
140
|
</aside>
|
|
141
141
|
<div class="banner" id="banner"></div>
|
|
@@ -202,7 +202,10 @@
|
|
|
202
202
|
noAgents: "No agents yet — click + New.", noBots: "No bots configured.", online: "✅ Online",
|
|
203
203
|
currentVersion: "Current version",
|
|
204
204
|
helpTitle: "📖 Help", tipReload: "💡 Click [↻ Reload Fleet] after changes to apply them.",
|
|
205
|
-
wnIntro: "Recent highlights:", releaseNotes: "📄 View full Release Notes →",
|
|
205
|
+
wnIntro: "Recent highlights:", releaseNotes: "📄 View full Release Notes →", languageButton: "Language", helpButton: "Help", showAdditional: "Show additional controls", hideAdvanced: "Hide advanced", showAdvanced: "Show advanced",
|
|
206
|
+
saved: "Saved", failed: "Failed", actionFailed: "{0} failed", noAgentMatch: "No agents match \"{0}\".", deleteAgent: "Delete agent \"{0}\"? This will be applied only after you confirm the pending changes.", nameRequired: "name required", tokenEnvRequired: "token env required", removeBot: "Remove bot \"{0}\"? (the token in .env is left untouched)",
|
|
207
|
+
changesPending: "{0} change(s) not applied", impactNow: "⚡ Immediately", impactAgent: "🔄 Restart this Agent", impactFleet: "🔄🔄 Restart AgEnD", queued: "Queued: {0}", applyFailed: "Apply failed", changesApplied: "✓ Changes applied. AgEnD is reconciling affected Agents.", reviewChanges: "Review changes", readyApply: "Ready to apply", copied: "Copied", expectedMapping: "expected a mapping", configLoadFailed: "Failed to load config — check the token in the URL.",
|
|
208
|
+
mustNonNegative: "Must be 0 or greater", mustPositive: "Must be greater than 0", nonnegativeInteger: "Must be a non-negative integer", failoverEmpty: "Empty list disables failover", concurrencyRange: "Must be an integer from 1 to 20", concurrencyWarning: "May exceed CPU capacity on smaller hosts", staggerRange: "Must be between 0 and 30000", staggerWarning: "Very low values can cause startup spikes",
|
|
206
209
|
},
|
|
207
210
|
"zh-TW": {
|
|
208
211
|
title: "⚙️ AgEnD 設定", reloadFleet: "↻ 重新載入", myAgents: "🤖 我的 Agent", bots: "🔌 機器人", classicChannels: "🎮 ClassicBot 頻道", noClassic: "沒有 ClassicBot 頻道 — 在聊天中用 /start 建立。",
|
|
@@ -220,7 +223,10 @@
|
|
|
220
223
|
noAgents: "還沒有 agent — 點 + 新增。", noBots: "尚未設定機器人。", online: "✅ 上線",
|
|
221
224
|
currentVersion: "目前版本",
|
|
222
225
|
helpTitle: "📖 使用說明", tipReload: "💡 修改後點 [↻ 重新載入] 讓設定生效。",
|
|
223
|
-
wnIntro: "近期更新重點:", releaseNotes: "📄 查看完整 Release Notes →",
|
|
226
|
+
wnIntro: "近期更新重點:", releaseNotes: "📄 查看完整 Release Notes →", languageButton: "語言", helpButton: "使用說明", showAdditional: "顯示更多控制項", hideAdvanced: "隱藏進階設定", showAdvanced: "顯示進階設定",
|
|
227
|
+
saved: "已儲存", failed: "失敗", actionFailed: "{0}失敗", noAgentMatch: "沒有符合「{0}」的 Agent。", deleteAgent: "確定刪除 Agent「{0}」嗎?只有在確認套用待處理變更後才會生效。", nameRequired: "必須填寫名稱", tokenEnvRequired: "必須填寫 token 環境變數", removeBot: "確定移除機器人「{0}」嗎?(.env 中的 token 會保留)",
|
|
228
|
+
changesPending: "有 {0} 項變更尚未套用", impactNow: "⚡ 立即生效", impactAgent: "🔄 重新啟動此 Agent", impactFleet: "🔄🔄 重新啟動 AgEnD", queued: "已加入待套用:{0}", applyFailed: "套用失敗", changesApplied: "✓ 變更已套用,AgEnD 正在協調受影響的 Agent。", reviewChanges: "檢查變更", readyApply: "可套用變更", copied: "已複製", expectedMapping: "預期為 mapping", configLoadFailed: "載入設定失敗 — 請檢查網址中的 token。",
|
|
229
|
+
mustNonNegative: "必須為 0 或更大的數值", mustPositive: "必須大於 0", nonnegativeInteger: "必須是非負整數", failoverEmpty: "空白清單會停用模型備援", concurrencyRange: "必須是 1 到 20 的整數", concurrencyWarning: "在較小的主機上可能超出 CPU 負荷", staggerRange: "必須介於 0 到 30000", staggerWarning: "數值太低可能造成啟動尖峰",
|
|
224
230
|
},
|
|
225
231
|
};
|
|
226
232
|
const HELP = {
|
|
@@ -242,9 +248,11 @@
|
|
|
242
248
|
let lang = localStorage.getItem("agend_lang") || (navigator.language && navigator.language.toLowerCase().startsWith("zh") ? "zh-TW" : "en");
|
|
243
249
|
if (!I18N[lang]) lang = "en";
|
|
244
250
|
const t = (k) => (I18N[lang] && I18N[lang][k]) || I18N.en[k] || k;
|
|
251
|
+
const tf = (k, ...values) => values.reduce((text, value, index) => text.replaceAll(`{${index}}`, String(value)), t(k));
|
|
245
252
|
function applyStaticI18n() {
|
|
246
253
|
document.querySelectorAll("[data-i18n]").forEach(e => { e.textContent = t(e.getAttribute("data-i18n")); });
|
|
247
254
|
document.querySelectorAll("[data-i18n-ph]").forEach(e => { e.setAttribute("placeholder", t(e.getAttribute("data-i18n-ph"))); });
|
|
255
|
+
document.querySelectorAll("[data-i18n-title]").forEach(e => { e.setAttribute("title", t(e.getAttribute("data-i18n-title"))); });
|
|
248
256
|
// toggle labels that carry expand/collapse state
|
|
249
257
|
for (const [id, body] of [["advToggle", "advBody"]]) {
|
|
250
258
|
const b = $(body); if (b) $(id).textContent = b.classList.contains("open") ? t("collapse") : t("expand");
|
|
@@ -292,7 +300,7 @@
|
|
|
292
300
|
const b = res.body || {}; let html = "";
|
|
293
301
|
if (b.errors && b.errors.length) html += b.errors.map(e => `<span class="e">✗ ${esc(e.path ? e.path + ": " : "")}${esc(e.message)}</span>`).join(" ");
|
|
294
302
|
if (b.warnings && b.warnings.length) html += " " + b.warnings.map(w => `<span class="w">⚠ ${esc(w.message)}</span>`).join(" ");
|
|
295
|
-
if (res.ok) html = `<span class="s">✓
|
|
303
|
+
if (res.ok) html = `<span class="s">✓ ${esc(t("saved"))}</span>` + (html ? " " + html : "");
|
|
296
304
|
else if (!html) html = `<span class="e">✗ ${esc((b && b.error) || ("HTTP " + res.status))}</span>`;
|
|
297
305
|
elm.innerHTML = html;
|
|
298
306
|
}
|
|
@@ -322,18 +330,19 @@
|
|
|
322
330
|
redraw(); return box;
|
|
323
331
|
}
|
|
324
332
|
function select(value, options) { const s = el("select"); for (const o of options) { const val = typeof o === "string" ? o : o.value; const label = typeof o === "string" ? o : o.label; const opt = el("option", {}, label); opt.value = val; if (val === value) opt.selected = true; s.append(opt); } return s; }
|
|
325
|
-
const
|
|
333
|
+
const impactText = (kind) => t(kind === "now" ? "impactNow" : kind === "fleet" ? "impactFleet" : "impactAgent");
|
|
334
|
+
const impact = (kind) => el("span", { class: "impact" }, impactText(kind));
|
|
326
335
|
function updatePendingBar() {
|
|
327
336
|
const n = state.pending.size;
|
|
328
337
|
$("pendingBar").classList.toggle("open", n > 0);
|
|
329
|
-
$("pendingCount").textContent =
|
|
338
|
+
$("pendingCount").textContent = tf("changesPending", n);
|
|
330
339
|
const kinds = new Set([...state.pending.values()].map(change => change.impact));
|
|
331
|
-
$("pendingImpact").textContent = [...kinds].map(
|
|
340
|
+
$("pendingImpact").textContent = [...kinds].map(impactText).join(" · ");
|
|
332
341
|
}
|
|
333
342
|
function stageChange(key, change) {
|
|
334
343
|
state.pending.set(key, change);
|
|
335
344
|
updatePendingBar();
|
|
336
|
-
showBanner(
|
|
345
|
+
showBanner(tf("queued", change.label));
|
|
337
346
|
}
|
|
338
347
|
async function applyPendingChanges() {
|
|
339
348
|
const button = $("applyChanges"); button.disabled = true;
|
|
@@ -342,14 +351,14 @@
|
|
|
342
351
|
try { result = await change.apply(); }
|
|
343
352
|
catch (error) { result = { ok: false, body: { error: error.message } }; }
|
|
344
353
|
if (!result?.ok) {
|
|
345
|
-
showBanner(`${change.label}: ${result?.body?.error || "
|
|
354
|
+
showBanner(`${change.label}: ${result?.body?.error || t("applyFailed")}`, true);
|
|
346
355
|
button.disabled = false; return;
|
|
347
356
|
}
|
|
348
357
|
state.pending.delete(key); updatePendingBar();
|
|
349
358
|
}
|
|
350
359
|
await api("/api/settings/reload", { method: "POST" });
|
|
351
360
|
await reload();
|
|
352
|
-
showBanner("
|
|
361
|
+
showBanner(t("changesApplied"));
|
|
353
362
|
button.disabled = false;
|
|
354
363
|
}
|
|
355
364
|
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj || {}, key);
|
|
@@ -424,7 +433,7 @@
|
|
|
424
433
|
const filter = ($("agentSearch")?.value || "").trim().toLowerCase();
|
|
425
434
|
let names = Object.keys(insts);
|
|
426
435
|
if (filter) names = names.filter(n => n.toLowerCase().includes(filter) || shortName(n, insts[n]).toLowerCase().includes(filter));
|
|
427
|
-
if (!names.length) { host.className = "card empty"; host.textContent = "
|
|
436
|
+
if (!names.length) { host.className = "card empty"; host.textContent = tf("noAgentMatch", filter); return; }
|
|
428
437
|
host.className = "card";
|
|
429
438
|
// Group by first tag; "Other" sorts last. Within a group: Running → Crashed
|
|
430
439
|
// → Stopped, then name.
|
|
@@ -474,9 +483,9 @@
|
|
|
474
483
|
const saveBtn = el("button", { class: "primary sm" }, t("save"));
|
|
475
484
|
const validate = () => {
|
|
476
485
|
const autoValue = Number(auto.input.value), hangValue = Number(hang.input.value);
|
|
477
|
-
const validAuto = setValidation(auto.input, autoFeedback, !auto.toggle.checked && (!Number.isFinite(autoValue) || autoValue < 0) ? "
|
|
478
|
-
const validHang = setValidation(hang.input, hangFeedback, !hang.toggle.checked && (!Number.isFinite(hangValue) || hangValue <= 0) ? "
|
|
479
|
-
setValidation(failover.input, failoverFeedback, "", !failover.toggle.checked && !failover.input.value.trim() ? "
|
|
486
|
+
const validAuto = setValidation(auto.input, autoFeedback, !auto.toggle.checked && (!Number.isFinite(autoValue) || autoValue < 0) ? t("mustNonNegative") : "");
|
|
487
|
+
const validHang = setValidation(hang.input, hangFeedback, !hang.toggle.checked && (!Number.isFinite(hangValue) || hangValue <= 0) ? t("mustPositive") : "");
|
|
488
|
+
setValidation(failover.input, failoverFeedback, "", !failover.toggle.checked && !failover.input.value.trim() ? t("failoverEmpty") : "");
|
|
480
489
|
saveBtn.disabled = !(validAuto && validHang);
|
|
481
490
|
return !saveBtn.disabled;
|
|
482
491
|
};
|
|
@@ -571,11 +580,11 @@
|
|
|
571
580
|
}
|
|
572
581
|
async function pauseWakeAgent(name, action) {
|
|
573
582
|
const res = await api(`/api/settings/instances/${encodeURIComponent(name)}/${action}`, { method: "POST" });
|
|
574
|
-
if (!res.ok) alert(res.body?.error ||
|
|
583
|
+
if (!res.ok) alert(res.body?.error || tf("actionFailed", action));
|
|
575
584
|
await reloadLive();
|
|
576
585
|
}
|
|
577
586
|
async function delAgent(name) {
|
|
578
|
-
if (!confirm(
|
|
587
|
+
if (!confirm(tf("deleteAgent", name))) return;
|
|
579
588
|
delete state.fleet.instances[name];
|
|
580
589
|
stageChange(`agent:${name}`, {
|
|
581
590
|
label: `Delete ${name}`, impact: "fleet",
|
|
@@ -596,7 +605,7 @@
|
|
|
596
605
|
el("div", { class: "grid2" }, el("div", {}, el("label", {}, t("nameField")), fName), el("div", {}, el("label", {}, t("backend")), fBackend)),
|
|
597
606
|
el("div", { class: "grid2" }, el("div", {}, el("label", {}, t("workingDir")), fWd), el("div", {}, el("label", {}, t("channelBinding")), fChan)),
|
|
598
607
|
el("div", { class: "actions" }, el("button", { class: "primary sm", onclick: async () => {
|
|
599
|
-
const name = fName.value.trim(); if (!name) { msg.innerHTML = `<span class="e">✗
|
|
608
|
+
const name = fName.value.trim(); if (!name) { msg.innerHTML = `<span class="e">✗ ${esc(t("nameRequired"))}</span>`; return; }
|
|
600
609
|
const body = { working_directory: fWd.value, backend: fBackend.value, channel_id: fChan.value || undefined };
|
|
601
610
|
const res = await api(`/api/settings/fleet/instances/${encodeURIComponent(name)}`, { method: "POST", body: JSON.stringify(body) });
|
|
602
611
|
resultMsg(msg, res); if (res.ok) { state.newAgentOpen = false; $("newAgentBtn").textContent = t("new"); renderNewAgent(false); await reload(); }
|
|
@@ -682,8 +691,8 @@
|
|
|
682
691
|
const validate = () => {
|
|
683
692
|
const autoValue = Number(auto.input.value);
|
|
684
693
|
const contextValue = Number(fContext.value);
|
|
685
|
-
const validAuto = setValidation(auto.input, autoFeedback, !auto.toggle.checked && (!Number.isFinite(autoValue) || autoValue < 0) ? "
|
|
686
|
-
const validContext = setValidation(fContext, contextFeedback, !Number.isInteger(contextValue) || contextValue < 0 ? "
|
|
694
|
+
const validAuto = setValidation(auto.input, autoFeedback, !auto.toggle.checked && (!Number.isFinite(autoValue) || autoValue < 0) ? t("mustNonNegative") : "");
|
|
695
|
+
const validContext = setValidation(fContext, contextFeedback, !Number.isInteger(contextValue) || contextValue < 0 ? t("nonnegativeInteger") : "");
|
|
687
696
|
saveBtn.disabled = !(validAuto && validContext);
|
|
688
697
|
return !saveBtn.disabled;
|
|
689
698
|
};
|
|
@@ -771,10 +780,10 @@
|
|
|
771
780
|
}
|
|
772
781
|
async function delBot(i) {
|
|
773
782
|
const chs = channels(); const ch = chs[i];
|
|
774
|
-
if (!confirm(
|
|
783
|
+
if (!confirm(tf("removeBot", `${ch.type} (${chLabel(i)})`))) return;
|
|
775
784
|
chs.splice(i, 1);
|
|
776
785
|
state.fleet.channels = chs; delete state.fleet.channel;
|
|
777
|
-
const res = await saveChannels(); if (res.ok) await reload(); else alert((res.body && res.body.error) || "
|
|
786
|
+
const res = await saveChannels(); if (res.ok) await reload(); else alert((res.body && res.body.error) || t("failed"));
|
|
778
787
|
}
|
|
779
788
|
function renderNewBot(open) {
|
|
780
789
|
const host = $("newBot"); host.innerHTML = ""; if (!open) return;
|
|
@@ -789,7 +798,7 @@
|
|
|
789
798
|
el("div", { class: "grid2" }, el("div", {}, el("label", {}, t("tokenEnv")), fEnv), el("div", {}, el("label", {}, t("groupId")), fGroup)),
|
|
790
799
|
el("div", { class: "hint" }, t("envHint")),
|
|
791
800
|
el("div", { class: "actions" }, el("button", { class: "primary sm", onclick: async () => {
|
|
792
|
-
if (!fEnv.value.trim()) { msg.innerHTML = `<span class="e">✗
|
|
801
|
+
if (!fEnv.value.trim()) { msg.innerHTML = `<span class="e">✗ ${esc(t("tokenEnvRequired"))}</span>`; return; }
|
|
793
802
|
const chs = channels();
|
|
794
803
|
chs.push({ id: fId.value.trim() || undefined, type: fType.value, bot_token_env: fEnv.value.trim(), group_id: fGroup.value.trim() || undefined, access: { mode: "locked", allowed_users: [] } });
|
|
795
804
|
state.fleet.channels = chs; delete state.fleet.channel;
|
|
@@ -840,17 +849,17 @@
|
|
|
840
849
|
const validate = () => {
|
|
841
850
|
const auto = Number(fAutoPause.value), timeout = Number(fHangTimeout.value);
|
|
842
851
|
const concurrency = Number(fConcurrency.value), stagger = Number(fStagger.value);
|
|
843
|
-
const validAuto = setValidation(fAutoPause, autoFeedback, !Number.isFinite(auto) || auto < 0 ? "
|
|
844
|
-
const validHang = setValidation(fHangTimeout, hangFeedback, !Number.isFinite(timeout) || timeout <= 0 ? "
|
|
845
|
-
const validConcurrency = setValidation(fConcurrency, concurrencyFeedback, !Number.isInteger(concurrency) || concurrency < 1 || concurrency > 20 ? "
|
|
846
|
-
const validStagger = setValidation(fStagger, staggerFeedback, !Number.isFinite(stagger) || stagger < 0 || stagger > 30000 ? "
|
|
852
|
+
const validAuto = setValidation(fAutoPause, autoFeedback, !Number.isFinite(auto) || auto < 0 ? t("mustNonNegative") : "");
|
|
853
|
+
const validHang = setValidation(fHangTimeout, hangFeedback, !Number.isFinite(timeout) || timeout <= 0 ? t("mustPositive") : "");
|
|
854
|
+
const validConcurrency = setValidation(fConcurrency, concurrencyFeedback, !Number.isInteger(concurrency) || concurrency < 1 || concurrency > 20 ? t("concurrencyRange") : "", concurrency > 4 ? t("concurrencyWarning") : "");
|
|
855
|
+
const validStagger = setValidation(fStagger, staggerFeedback, !Number.isFinite(stagger) || stagger < 0 || stagger > 30000 ? t("staggerRange") : "", stagger < 100 ? t("staggerWarning") : "");
|
|
847
856
|
saveBtn.disabled = !(validAuto && validHang && validConcurrency && validStagger);
|
|
848
857
|
return !saveBtn.disabled;
|
|
849
858
|
};
|
|
850
859
|
for (const input of [fAutoPause, fHangTimeout, fConcurrency, fStagger]) {
|
|
851
860
|
input.addEventListener("input", validate); input.addEventListener("change", validate);
|
|
852
861
|
}
|
|
853
|
-
saveBtn.textContent = "
|
|
862
|
+
saveBtn.textContent = t("reviewChanges");
|
|
854
863
|
saveBtn.onclick = () => {
|
|
855
864
|
if (!validate()) return;
|
|
856
865
|
const nextDefaults = {
|
|
@@ -896,7 +905,7 @@
|
|
|
896
905
|
if (Object.keys(classicPatch).length > 0) {
|
|
897
906
|
stageChange("defaults:classic", { label: "Update ClassicBot defaults and access", impact: "fleet", apply: () => api("/api/settings/classic/defaults", { method: "PUT", body: JSON.stringify(classicPatch) }) });
|
|
898
907
|
}
|
|
899
|
-
msg.innerHTML = `<span class="s"
|
|
908
|
+
msg.innerHTML = `<span class="s">${esc(t("readyApply"))}</span>`;
|
|
900
909
|
};
|
|
901
910
|
|
|
902
911
|
const section = (title, open, ...children) => {
|
|
@@ -966,7 +975,7 @@
|
|
|
966
975
|
el("button", { class: "sm" + (fmt === "yaml" ? " on" : ""), onclick: () => { state.advFmt = "yaml"; renderAdvanced(); } }, "YAML"),
|
|
967
976
|
el("button", { class: "sm" + (fmt === "json" ? " on" : ""), onclick: () => { state.advFmt = "json"; renderAdvanced(); } }, "JSON")),
|
|
968
977
|
el("button", { class: "sm", onclick: () => { state.advEdit = !state.advEdit; renderAdvanced(); } }, state.advEdit ? "View" : "Edit"),
|
|
969
|
-
el("button", { class: "sm", onclick: () => navigator.clipboard.writeText(ser).then(() => { msg.innerHTML = `<span class="s"
|
|
978
|
+
el("button", { class: "sm", onclick: () => navigator.clipboard.writeText(ser).then(() => { msg.innerHTML = `<span class="s">${esc(t("copied"))}</span>`; }) }, "Copy"),
|
|
970
979
|
el("button", { class: "sm", onclick: () => { const blob = new Blob([toYaml(fleetModel())], { type: "text/plain" }); const a = el("a", { href: URL.createObjectURL(blob), download: "fleet.yaml" }); a.click(); URL.revokeObjectURL(a.href); } }, "⬇ fleet.yaml"),
|
|
971
980
|
);
|
|
972
981
|
host.append(toolbar);
|
|
@@ -982,7 +991,7 @@
|
|
|
982
991
|
}
|
|
983
992
|
// Apply the edited full model: PUT defaults + channels, and diff instances.
|
|
984
993
|
async function applyFullModel(m, msg) {
|
|
985
|
-
if (!m || typeof m !== "object") { msg.innerHTML = `<span class="e">✗
|
|
994
|
+
if (!m || typeof m !== "object") { msg.innerHTML = `<span class="e">✗ ${esc(t("expectedMapping"))}</span>`; return; }
|
|
986
995
|
const results = [];
|
|
987
996
|
if (m.defaults) results.push(await api("/api/settings/fleet/defaults", { method: "PUT", body: JSON.stringify(m.defaults) }));
|
|
988
997
|
const chs = m.channels || (m.channel ? [m.channel] : null);
|
|
@@ -1020,7 +1029,7 @@
|
|
|
1020
1029
|
async function reload() {
|
|
1021
1030
|
// Forms edit the user-authored config, not defaults-expanded runtime values.
|
|
1022
1031
|
const f = await api("/api/settings/fleet/raw");
|
|
1023
|
-
if (!f.ok) { $("banner").style.display = "block"; $("banner").style.background = "#fef2f2"; $("banner").style.color = "var(--red)"; $("banner").textContent = "
|
|
1032
|
+
if (!f.ok) { $("banner").style.display = "block"; $("banner").style.background = "#fef2f2"; $("banner").style.color = "var(--red)"; $("banner").textContent = t("configLoadFailed"); return; }
|
|
1024
1033
|
state.fleet = f.body || {};
|
|
1025
1034
|
const c = await api("/api/settings/classic"); state.classic = c.body || {};
|
|
1026
1035
|
await reloadLive();
|
|
@@ -1039,12 +1048,12 @@
|
|
|
1039
1048
|
$("advToggle").onclick = () => { const b = $("advBody"); const open = !b.classList.contains("open"); b.classList.toggle("open", open); $("advToggle").textContent = open ? t("collapse") : t("expand"); };
|
|
1040
1049
|
const advancedEnabled = localStorage.getItem("agend_settings_advanced") === "true";
|
|
1041
1050
|
document.body.classList.toggle("show-advanced", advancedEnabled);
|
|
1042
|
-
$("advancedToggle").textContent = advancedEnabled ? "
|
|
1051
|
+
$("advancedToggle").textContent = advancedEnabled ? t("hideAdvanced") : t("showAdvanced");
|
|
1043
1052
|
$("advancedToggle").onclick = () => {
|
|
1044
1053
|
const enabled = !document.body.classList.contains("show-advanced");
|
|
1045
1054
|
document.body.classList.toggle("show-advanced", enabled);
|
|
1046
1055
|
localStorage.setItem("agend_settings_advanced", String(enabled));
|
|
1047
|
-
$("advancedToggle").textContent = enabled ? "
|
|
1056
|
+
$("advancedToggle").textContent = enabled ? t("hideAdvanced") : t("showAdvanced");
|
|
1048
1057
|
};
|
|
1049
1058
|
$("applyChanges").onclick = applyPendingChanges;
|
|
1050
1059
|
$("discardChanges").onclick = async () => { state.pending.clear(); updatePendingBar(); await reload(); };
|
package/dist/ui/view.html
CHANGED
|
@@ -129,17 +129,17 @@
|
|
|
129
129
|
</style>
|
|
130
130
|
</head>
|
|
131
131
|
<body>
|
|
132
|
-
<button class="help-btn" id="usageBtn" title="AI usage" style="right:140px; display:none">📊</button>
|
|
133
|
-
<button class="help-btn" id="densityBtn" title="Font density" style="right:98px">⤢</button>
|
|
134
|
-
<button class="help-btn" id="langBtn" title="Language" style="right:56px">🌐</button>
|
|
135
|
-
<button class="help-btn" id="helpBtn" title="Help">ℹ️</button>
|
|
132
|
+
<button class="help-btn" id="usageBtn" data-i18n-title="usageButton" title="AI usage" style="right:140px; display:none">📊</button>
|
|
133
|
+
<button class="help-btn" id="densityBtn" data-i18n-title="densityLabel" title="Font density" style="right:98px">⤢</button>
|
|
134
|
+
<button class="help-btn" id="langBtn" data-i18n-title="languageButton" title="Language" style="right:56px">🌐</button>
|
|
135
|
+
<button class="help-btn" id="helpBtn" data-i18n-title="helpButton" title="Help">ℹ️</button>
|
|
136
136
|
<div class="help-backdrop" id="helpBackdrop"></div>
|
|
137
137
|
<aside class="help-panel" id="helpPanel">
|
|
138
|
-
<button class="close" id="helpClose" title="Close">×</button>
|
|
138
|
+
<button class="close" id="helpClose" data-i18n-title="close" title="Close">×</button>
|
|
139
139
|
<div id="helpContent"></div>
|
|
140
140
|
</aside>
|
|
141
141
|
<aside class="help-panel" id="usagePanel">
|
|
142
|
-
<button class="close" id="usageClose" title="Close">×</button>
|
|
142
|
+
<button class="close" id="usageClose" data-i18n-title="close" title="Close">×</button>
|
|
143
143
|
<div id="usageContent"></div>
|
|
144
144
|
</aside>
|
|
145
145
|
<div id="sidebar"><h1 data-i18n="viewTitle">AgEnD · View</h1><div id="list"></div></div>
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
<div class="meta" id="meta"></div>
|
|
153
153
|
<div class="desc" id="desc"></div>
|
|
154
154
|
</div>
|
|
155
|
-
<button id="cardToggle" title="Expand profile" aria-expanded="false">▲</button>
|
|
155
|
+
<button id="cardToggle" data-i18n-title="expandProfile" title="Expand profile" aria-expanded="false">▲</button>
|
|
156
156
|
<button id="editBtn" data-i18n="editProfile">✏️ Edit</button>
|
|
157
157
|
<div id="edit">
|
|
158
158
|
<input id="fDisplay" data-i18n-ph="displayName" placeholder="Display name">
|
|
@@ -193,7 +193,10 @@
|
|
|
193
193
|
tooltipBackend: "Backend: {value}", tooltipStatus: "Status: {value}", tooltipContext: "Context: {value}",
|
|
194
194
|
tooltipUnavailable: "Unavailable", statusRunning: "Running", statusPaused: "Paused",
|
|
195
195
|
statusStopped: "Stopped", statusCrashed: "Crashed", statusUnknown: "Unknown",
|
|
196
|
-
usageResets: "resets in {t}", usageSoon: "resets soon", usageFail: "Could not load usage data."
|
|
196
|
+
usageResets: "resets in {t}", usageSoon: "resets soon", usageFail: "Could not load usage data.",
|
|
197
|
+
usageButton: "AI usage", languageButton: "Language", helpButton: "Help", close: "Close", expandProfile: "Expand profile",
|
|
198
|
+
rosterFailed: "Failed to load roster: {error}\n(Is the fleet running?)", paneFailed: "⚠ Could not load pane for {name} (HTTP {status}).",
|
|
199
|
+
tokenRequired: "web.token required", avatarFailed: "Avatar upload failed: {error}", saved: "Saved", saveFailed: "Save failed: {error}" },
|
|
197
200
|
"zh-TW": { viewTitle: "AgEnD · 檢視", editProfile: "✏️ 編輯", save: "儲存", cancel: "取消",
|
|
198
201
|
displayName: "顯示名稱", role: "角色", description: "描述", tokenPh: "web.token(儲存時需要)",
|
|
199
202
|
saveHint: "檢視為公開(唯讀)。儲存需要 web.token(~/.agend/web.token)。", helpTitle: "📖 使用說明",
|
|
@@ -208,14 +211,19 @@
|
|
|
208
211
|
tooltipBackend: "後端:{value}", tooltipStatus: "狀態:{value}", tooltipContext: "上下文:{value}",
|
|
209
212
|
tooltipUnavailable: "無資料", statusRunning: "運行中", statusPaused: "已暫停",
|
|
210
213
|
statusStopped: "已停止", statusCrashed: "已崩潰", statusUnknown: "未知",
|
|
211
|
-
usageResets: "{t} 後重置", usageSoon: "即將重置", usageFail: "無法載入用量資料。"
|
|
214
|
+
usageResets: "{t} 後重置", usageSoon: "即將重置", usageFail: "無法載入用量資料。",
|
|
215
|
+
usageButton: "AI 訂閱用量", languageButton: "語言", helpButton: "使用說明", close: "關閉", expandProfile: "展開個人資料",
|
|
216
|
+
rosterFailed: "載入 instance 清單失敗:{error}\n(Fleet 是否正在執行?)", paneFailed: "⚠ 無法載入 {name} 的 pane(HTTP {status})。",
|
|
217
|
+
tokenRequired: "需要 web.token", avatarFailed: "上傳頭像失敗:{error}", saved: "已儲存", saveFailed: "儲存失敗:{error}" },
|
|
212
218
|
};
|
|
213
219
|
let lang = localStorage.getItem("agend_lang") || (navigator.language && navigator.language.toLowerCase().startsWith("zh") ? "zh-TW" : "en");
|
|
214
220
|
if (!I18N[lang]) lang = "en";
|
|
215
221
|
const T = (k) => (I18N[lang] && I18N[lang][k]) || I18N.en[k] || k;
|
|
222
|
+
const TF = (k, values = {}) => Object.entries(values).reduce((s, [name, value]) => s.replaceAll(`{${name}}`, String(value)), T(k));
|
|
216
223
|
function applyI18n() {
|
|
217
224
|
document.querySelectorAll("[data-i18n]").forEach(e => { e.textContent = T(e.getAttribute("data-i18n")); });
|
|
218
225
|
document.querySelectorAll("[data-i18n-ph]").forEach(e => { e.setAttribute("placeholder", T(e.getAttribute("data-i18n-ph"))); });
|
|
226
|
+
document.querySelectorAll("[data-i18n-title]").forEach(e => { e.setAttribute("title", T(e.getAttribute("data-i18n-title"))); });
|
|
219
227
|
const hc = q("helpContent");
|
|
220
228
|
if (hc) { hc.innerHTML = ""; const h3 = document.createElement("h3"); h3.textContent = T("helpTitle"); hc.append(h3);
|
|
221
229
|
const ul = document.createElement("ul"); for (const k of ["help1","help2","help3","help4","help5"]) { const li = document.createElement("li"); li.textContent = T(k); ul.append(li); } hc.append(ul); }
|
|
@@ -557,7 +565,7 @@
|
|
|
557
565
|
if (!current && roster.length) current = roster[0].instance_name;
|
|
558
566
|
computeOrder();
|
|
559
567
|
renderList(); renderCard();
|
|
560
|
-
} catch (e) { q("pre").textContent = "
|
|
568
|
+
} catch (e) { q("pre").textContent = TF("rosterFailed", { error: e.message }); }
|
|
561
569
|
}
|
|
562
570
|
|
|
563
571
|
// ── Terminal ──────────────────────────────────────────
|
|
@@ -575,7 +583,7 @@
|
|
|
575
583
|
if (!r.ok) {
|
|
576
584
|
// Surface the failure instead of leaving the previous instance's output
|
|
577
585
|
// on screen (which reads as "switching did nothing").
|
|
578
|
-
q("pre").textContent =
|
|
586
|
+
q("pre").textContent = TF("paneFailed", { name: target, status: r.status });
|
|
579
587
|
return;
|
|
580
588
|
}
|
|
581
589
|
const text = await r.text();
|
|
@@ -714,7 +722,7 @@
|
|
|
714
722
|
const it = roster.find(r => r.instance_name === current);
|
|
715
723
|
if (!it) return;
|
|
716
724
|
const wt = q("fToken").value.trim();
|
|
717
|
-
if (!wt) { q("msg").className = "err"; q("msg").textContent = "
|
|
725
|
+
if (!wt) { q("msg").className = "err"; q("msg").textContent = T("tokenRequired"); return; }
|
|
718
726
|
localStorage.setItem("agend_web_token", wt);
|
|
719
727
|
const name = encodeURIComponent(current);
|
|
720
728
|
try {
|
|
@@ -727,12 +735,12 @@
|
|
|
727
735
|
const file = q("fAvatar").files[0];
|
|
728
736
|
if (file) {
|
|
729
737
|
const ar = await fetch(`/api/avatar/${name}`, { method: "POST", headers: { "Content-Type": file.type, "X-Agend-Token": wt }, body: file });
|
|
730
|
-
if (!ar.ok) { const e = await ar.json().catch(() => ({})); throw new Error("
|
|
738
|
+
if (!ar.ok) { const e = await ar.json().catch(() => ({})); throw new Error(TF("avatarFailed", { error: e.error || ar.status })); }
|
|
731
739
|
}
|
|
732
|
-
q("msg").className = "ok"; q("msg").textContent = "
|
|
740
|
+
q("msg").className = "ok"; q("msg").textContent = T("saved");
|
|
733
741
|
await loadRoster();
|
|
734
742
|
setTimeout(exitEdit, 500);
|
|
735
|
-
} catch (e) { q("msg").className = "err"; q("msg").textContent = e.message; }
|
|
743
|
+
} catch (e) { q("msg").className = "err"; q("msg").textContent = e.message.startsWith(T("avatarFailed").split("{")[0]) ? e.message : TF("saveFailed", { error: e.message }); }
|
|
736
744
|
}
|
|
737
745
|
|
|
738
746
|
q("editBtn").onclick = enterEdit;
|