@kevin5251984/guild 0.2.20 → 0.2.22
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/cordis.yml +2 -0
- package/package.json +1 -1
- package/src/cron-schedule.ts +388 -0
- package/src/cron.ts +403 -0
- package/src/db.ts +135 -0
- package/src/generate.ts +9 -0
- package/src/handlers.ts +102 -5
- package/src/harness.ts +25 -5
- package/src/llm.ts +1 -1
- package/src/plugins/cron.ts +26 -0
- package/src/public/chat.css +118 -9
- package/src/public/chat.html +475 -43
- package/src/public/i18n.js +23 -0
- package/src/public/md.js +109 -15
- package/src/public/mobile.css +116 -2
- package/src/public/mobile.html +185 -15
- package/src/router.ts +161 -2
- package/src/store.ts +116 -33
- package/src/subagent.ts +3 -0
- package/src/tools.ts +57 -1
- package/vendor/protocol/src/index.ts +7 -2
package/src/public/chat.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="icon" href="/favicon-32.png" type="image/png" sizes="32x32" />
|
|
9
9
|
<link rel="icon" href="/favicon-16.png" type="image/png" sizes="16x16" />
|
|
10
10
|
<title>Guild — 大廳</title>
|
|
11
|
-
<link rel="stylesheet" href="/chat.css?v=
|
|
11
|
+
<link rel="stylesheet" href="/chat.css?v=nav-fold" />
|
|
12
12
|
<script src="/i18n.js"></script>
|
|
13
13
|
<script src="/md.js"></script>
|
|
14
14
|
</head>
|
|
@@ -18,17 +18,38 @@
|
|
|
18
18
|
<div class="side-top">
|
|
19
19
|
<input class="search" id="filter" placeholder="搜尋" data-i18n-placeholder="search" />
|
|
20
20
|
</div>
|
|
21
|
-
<div class="
|
|
22
|
-
<
|
|
23
|
-
|
|
21
|
+
<div class="nav-sec" data-nav-sec="channels">
|
|
22
|
+
<div class="section-h">
|
|
23
|
+
<button type="button" class="sec-fold" data-fold="channels" aria-expanded="true" aria-controls="channels">
|
|
24
|
+
<span class="sec-chev" aria-hidden="true">▾</span>
|
|
25
|
+
<span data-i18n="channels">委託</span>
|
|
26
|
+
</button>
|
|
27
|
+
<button type="button" class="add-ch" id="toggle-create" title="新委託" data-i18n-title="newChannel">+</button>
|
|
28
|
+
</div>
|
|
29
|
+
<form class="create-row" id="create-row">
|
|
30
|
+
<input id="new-channel" placeholder="委託名稱" data-i18n-placeholder="newChannelName" />
|
|
31
|
+
<button type="submit" data-i18n="create">建立</button>
|
|
32
|
+
</form>
|
|
33
|
+
<ul class="navlist" id="channels"></ul>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="nav-sec" data-nav-sec="dms">
|
|
36
|
+
<div class="section-h">
|
|
37
|
+
<button type="button" class="sec-fold" data-fold="dms" aria-expanded="true" aria-controls="dms">
|
|
38
|
+
<span class="sec-chev" aria-hidden="true">▾</span>
|
|
39
|
+
<span data-i18n="dms">密談</span>
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
<ul class="navlist" id="dms"></ul>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="nav-sec" data-nav-sec="cron">
|
|
45
|
+
<div class="section-h">
|
|
46
|
+
<button type="button" class="sec-fold" data-fold="cron" aria-expanded="true" aria-controls="cron-nav">
|
|
47
|
+
<span class="sec-chev" aria-hidden="true">▾</span>
|
|
48
|
+
<span data-i18n="cron.title">排程</span>
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
<ul class="navlist" id="cron-nav"></ul>
|
|
24
52
|
</div>
|
|
25
|
-
<form class="create-row" id="create-row">
|
|
26
|
-
<input id="new-channel" placeholder="委託名稱" data-i18n-placeholder="newChannelName" />
|
|
27
|
-
<button type="submit" data-i18n="create">建立</button>
|
|
28
|
-
</form>
|
|
29
|
-
<ul class="navlist" id="channels"></ul>
|
|
30
|
-
<div class="section-h" data-i18n="dms">密談</div>
|
|
31
|
-
<ul class="navlist" id="dms"></ul>
|
|
32
53
|
<div class="side-foot">
|
|
33
54
|
<a href="/studio" data-i18n="nav.studio">編制</a>
|
|
34
55
|
<a href="/library" data-i18n="nav.library">工坊</a>
|
|
@@ -116,6 +137,7 @@
|
|
|
116
137
|
<button type="button" class="attach-item" data-attach="rules"><span class="attach-ico">☰</span><span data-i18n="attach.rules">規則</span></button>
|
|
117
138
|
<button type="button" class="attach-item" data-attach="term"><span class="attach-ico">>_</span><span data-i18n="attach.term">終端</span></button>
|
|
118
139
|
<button type="button" class="attach-item" data-attach="tree"><span class="attach-ico">🗺</span><span data-i18n="attach.tree">工作區</span></button>
|
|
140
|
+
<button type="button" class="attach-item" data-attach="cron"><span class="attach-ico">⏱</span><span data-i18n="attach.cron">排程</span></button>
|
|
119
141
|
<button type="button" class="attach-item" data-attach="slash"><span class="attach-ico">/</span><span data-i18n="attach.slash">指令</span></button>
|
|
120
142
|
</div>
|
|
121
143
|
</div>
|
|
@@ -230,6 +252,19 @@
|
|
|
230
252
|
<button type="button" id="channel-md-save" data-i18n="save">儲存</button>
|
|
231
253
|
</div>
|
|
232
254
|
</dialog>
|
|
255
|
+
<dialog id="cron-dialog">
|
|
256
|
+
<h2 data-i18n="cron.title">排程</h2>
|
|
257
|
+
<p data-i18n="cron.help">新增會請這席呼叫 cronjob。時間用自然語言寫即可,例如「每10分鐘跟我說一次 Hi」。guildd 每 60 秒看一次;到點後這席開一輪新的 @handle,prompt 必須自包含。</p>
|
|
258
|
+
<div id="cron-list" class="cron-list"></div>
|
|
259
|
+
<label for="cron-bot" data-i18n="cron.seat">席</label>
|
|
260
|
+
<select id="cron-bot"></select>
|
|
261
|
+
<label for="cron-ask" data-i18n="cron.ask">要排什麼</label>
|
|
262
|
+
<textarea id="cron-ask" rows="4" placeholder="每10分鐘跟我說一次 Hi" data-i18n-placeholder="cron.askPh"></textarea>
|
|
263
|
+
<div class="dialog-actions">
|
|
264
|
+
<button type="button" class="ghost" id="cron-cancel" data-i18n="cancel">取消</button>
|
|
265
|
+
<button type="button" id="cron-add" data-i18n="cron.add">請他排</button>
|
|
266
|
+
</div>
|
|
267
|
+
</dialog>
|
|
233
268
|
<dialog id="bot-memory">
|
|
234
269
|
<h2 data-i18n="memory.bot">MEMORY.md</h2>
|
|
235
270
|
<p data-i18n="memory.botHelp">這名冒險者學會的站立筆記。有用的回合後會自動更新。</p>
|
|
@@ -320,6 +355,19 @@
|
|
|
320
355
|
return new Set();
|
|
321
356
|
}
|
|
322
357
|
})(),
|
|
358
|
+
cronJobs: [],
|
|
359
|
+
navFold: (function () {
|
|
360
|
+
try {
|
|
361
|
+
const raw = JSON.parse(localStorage.getItem("guild-nav-fold") || "{}");
|
|
362
|
+
return {
|
|
363
|
+
channels: Boolean(raw.channels),
|
|
364
|
+
dms: Boolean(raw.dms),
|
|
365
|
+
cron: Boolean(raw.cron),
|
|
366
|
+
};
|
|
367
|
+
} catch {
|
|
368
|
+
return { channels: false, dms: false, cron: false };
|
|
369
|
+
}
|
|
370
|
+
})(),
|
|
323
371
|
};
|
|
324
372
|
|
|
325
373
|
function hue(key) {
|
|
@@ -1292,8 +1340,8 @@
|
|
|
1292
1340
|
}
|
|
1293
1341
|
function botModelMeta(bot) {
|
|
1294
1342
|
const model = botModelLabel(bot) || "—";
|
|
1343
|
+
const ref = botModelRef(bot);
|
|
1295
1344
|
const spec = (() => {
|
|
1296
|
-
const ref = botModelRef(bot);
|
|
1297
1345
|
if (!ref) return null;
|
|
1298
1346
|
const hit = allModels().find(
|
|
1299
1347
|
(row) => row.provider === ref.provider && row.model === ref.model,
|
|
@@ -1301,7 +1349,7 @@
|
|
|
1301
1349
|
return (hit && hit.reasoning) || null;
|
|
1302
1350
|
})();
|
|
1303
1351
|
const reasonVal = clampEffort(
|
|
1304
|
-
|
|
1352
|
+
ref && ref.reasoning,
|
|
1305
1353
|
spec,
|
|
1306
1354
|
Boolean(modelCfg && modelCfg.fast),
|
|
1307
1355
|
);
|
|
@@ -1422,6 +1470,65 @@
|
|
|
1422
1470
|
const nb = Number.isFinite(tb) ? tb : 0;
|
|
1423
1471
|
return nb - na;
|
|
1424
1472
|
}
|
|
1473
|
+
function navSecFolded(sec) {
|
|
1474
|
+
const q = (document.getElementById("filter").value || "").trim();
|
|
1475
|
+
if (q) return false;
|
|
1476
|
+
return Boolean(state.navFold[sec]);
|
|
1477
|
+
}
|
|
1478
|
+
function applyNavFold() {
|
|
1479
|
+
["channels", "dms", "cron"].forEach((sec) => {
|
|
1480
|
+
const el = document.querySelector('[data-nav-sec="' + sec + '"]');
|
|
1481
|
+
if (!el) return;
|
|
1482
|
+
const folded = navSecFolded(sec);
|
|
1483
|
+
el.classList.toggle("folded", folded);
|
|
1484
|
+
const btn = el.querySelector("[data-fold]");
|
|
1485
|
+
if (btn) btn.setAttribute("aria-expanded", folded ? "false" : "true");
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
function saveNavFold() {
|
|
1489
|
+
try {
|
|
1490
|
+
localStorage.setItem("guild-nav-fold", JSON.stringify(state.navFold));
|
|
1491
|
+
} catch {
|
|
1492
|
+
/* ignore quota */
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
function cronJobHref(job) {
|
|
1496
|
+
const id = String((job && job.roomId) || "");
|
|
1497
|
+
if ((state.channels || []).some((ch) => ch.id === id)) {
|
|
1498
|
+
return "#c/" + encodeURIComponent(id);
|
|
1499
|
+
}
|
|
1500
|
+
if (id.indexOf("dm-") === 0) {
|
|
1501
|
+
return "#d/" + encodeURIComponent(id.slice(3));
|
|
1502
|
+
}
|
|
1503
|
+
return "#c/" + encodeURIComponent(id);
|
|
1504
|
+
}
|
|
1505
|
+
function cronNavPreview(job) {
|
|
1506
|
+
const bot = botById(job.botId);
|
|
1507
|
+
const handle = bot ? bot.handle : "bot";
|
|
1508
|
+
return t("cron.navWhere", {
|
|
1509
|
+
handle: handle,
|
|
1510
|
+
schedule: job.schedule || "",
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
async function loadNavCron() {
|
|
1514
|
+
try {
|
|
1515
|
+
const res = await fetch("/cron");
|
|
1516
|
+
const body = await res.json().catch(() => ({}));
|
|
1517
|
+
const jobs = Array.isArray(body.jobs) ? body.jobs : [];
|
|
1518
|
+
const sig = jobs
|
|
1519
|
+
.map((job) =>
|
|
1520
|
+
[job.id, job.nextRunAt, job.paused ? "1" : "0", job.running ? "1" : "0", job.name].join(":"),
|
|
1521
|
+
)
|
|
1522
|
+
.join("|");
|
|
1523
|
+
if (sig === state.cronSig) return;
|
|
1524
|
+
state.cronSig = sig;
|
|
1525
|
+
state.cronJobs = jobs;
|
|
1526
|
+
} catch {
|
|
1527
|
+
if (state.cronSig != null) return;
|
|
1528
|
+
state.cronJobs = [];
|
|
1529
|
+
}
|
|
1530
|
+
renderNav();
|
|
1531
|
+
}
|
|
1425
1532
|
function renderNav() {
|
|
1426
1533
|
const q = document.getElementById("filter").value.toLowerCase();
|
|
1427
1534
|
const all = state.channels || [];
|
|
@@ -1544,6 +1651,53 @@
|
|
|
1544
1651
|
});
|
|
1545
1652
|
})
|
|
1546
1653
|
.join("");
|
|
1654
|
+
const cronBox = document.getElementById("cron-nav");
|
|
1655
|
+
if (cronBox) {
|
|
1656
|
+
const jobs = (state.cronJobs || []).filter((job) => {
|
|
1657
|
+
if (!q) return true;
|
|
1658
|
+
const bot = botById(job.botId);
|
|
1659
|
+
const hay = [
|
|
1660
|
+
job.name,
|
|
1661
|
+
job.schedule,
|
|
1662
|
+
job.prompt,
|
|
1663
|
+
bot && bot.handle,
|
|
1664
|
+
bot && bot.name,
|
|
1665
|
+
]
|
|
1666
|
+
.filter(Boolean)
|
|
1667
|
+
.join(" ")
|
|
1668
|
+
.toLowerCase();
|
|
1669
|
+
return hay.includes(q);
|
|
1670
|
+
});
|
|
1671
|
+
cronBox.innerHTML = jobs.length
|
|
1672
|
+
? jobs
|
|
1673
|
+
.map((job) => {
|
|
1674
|
+
const bot = botById(job.botId);
|
|
1675
|
+
const onRoom =
|
|
1676
|
+
cronJobHref(job) ===
|
|
1677
|
+
(state.kind === "dm"
|
|
1678
|
+
? "#d/" + encodeURIComponent(state.id)
|
|
1679
|
+
: "#c/" + encodeURIComponent(state.id));
|
|
1680
|
+
return navRowHtml({
|
|
1681
|
+
href: cronJobHref(job),
|
|
1682
|
+
on: onRoom,
|
|
1683
|
+
av: bot
|
|
1684
|
+
? '<span class="avatar" style="background:' +
|
|
1685
|
+
hue(bot.handle) +
|
|
1686
|
+
'">' +
|
|
1687
|
+
avatarFace(bot) +
|
|
1688
|
+
"</span>"
|
|
1689
|
+
: "",
|
|
1690
|
+
name: job.name || t("cron.title"),
|
|
1691
|
+
updatedAt: job.nextRunAt,
|
|
1692
|
+
preview: cronNavPreview(job),
|
|
1693
|
+
busy: Boolean(job.running),
|
|
1694
|
+
chip: job.paused ? t("cron.pause") : "",
|
|
1695
|
+
});
|
|
1696
|
+
})
|
|
1697
|
+
.join("")
|
|
1698
|
+
: '<li class="nav-empty">' + escapeHtml(t("cron.navEmpty")) + "</li>";
|
|
1699
|
+
}
|
|
1700
|
+
applyNavFold();
|
|
1547
1701
|
}
|
|
1548
1702
|
|
|
1549
1703
|
function memberRowHtml(bot, action, locked, full) {
|
|
@@ -1713,6 +1867,7 @@
|
|
|
1713
1867
|
thinking: row.thinking || "",
|
|
1714
1868
|
steps: Array.isArray(row.steps) ? row.steps.slice(-5) : [],
|
|
1715
1869
|
startedAt: row.startedAt || "",
|
|
1870
|
+
paused: Boolean(row.paused),
|
|
1716
1871
|
}));
|
|
1717
1872
|
}
|
|
1718
1873
|
function rebuildBusyBots() {
|
|
@@ -1741,6 +1896,7 @@
|
|
|
1741
1896
|
thinking: row.thinking || "",
|
|
1742
1897
|
steps: Array.isArray(row.steps) ? row.steps.slice(-5) : [],
|
|
1743
1898
|
startedAt: row.startedAt || "",
|
|
1899
|
+
paused: Boolean(row.paused),
|
|
1744
1900
|
room: { kind: r.kind, id: r.id },
|
|
1745
1901
|
}));
|
|
1746
1902
|
state.lives = others.concat(tagged);
|
|
@@ -1791,24 +1947,38 @@
|
|
|
1791
1947
|
? '<span class="clock">' + Math.floor(elapsed / 1000) + "s</span>"
|
|
1792
1948
|
: "";
|
|
1793
1949
|
const botId = live && live.botId ? live.botId : "";
|
|
1950
|
+
const paused = Boolean(live && live.paused);
|
|
1794
1951
|
const actions = botId
|
|
1795
1952
|
? '<div class="turn-actions">' +
|
|
1953
|
+
(paused
|
|
1954
|
+
? '<button type="button" class="turn-continue" data-live-continue="' +
|
|
1955
|
+
escapeHtml(botId) +
|
|
1956
|
+
'">' +
|
|
1957
|
+
t("live.continue") +
|
|
1958
|
+
"</button>"
|
|
1959
|
+
: '<button type="button" class="turn-pause" data-live-pause="' +
|
|
1960
|
+
escapeHtml(botId) +
|
|
1961
|
+
'">' +
|
|
1962
|
+
t("live.pause") +
|
|
1963
|
+
"</button>") +
|
|
1796
1964
|
'<button type="button" class="turn-stop" data-live-stop="' +
|
|
1797
1965
|
escapeHtml(botId) +
|
|
1798
1966
|
'">' +
|
|
1799
1967
|
t("live.stop") +
|
|
1800
1968
|
"</button>" +
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1969
|
+
(paused
|
|
1970
|
+
? ""
|
|
1971
|
+
: '<button type="button" class="turn-steer" data-live-steer="' +
|
|
1972
|
+
escapeHtml(botId) +
|
|
1973
|
+
'">' +
|
|
1974
|
+
t("live.steer") +
|
|
1975
|
+
"</button>") +
|
|
1806
1976
|
"</div>"
|
|
1807
1977
|
: "";
|
|
1808
1978
|
return (
|
|
1809
1979
|
'<div class="turn-live">' +
|
|
1810
1980
|
'<div class="turn-status">' +
|
|
1811
|
-
t("deepDiving") +
|
|
1981
|
+
(paused ? t("live.paused") : t("deepDiving")) +
|
|
1812
1982
|
clock +
|
|
1813
1983
|
"</div>" +
|
|
1814
1984
|
liveStepsHtml(live && live.steps) +
|
|
@@ -1829,7 +1999,9 @@
|
|
|
1829
1999
|
return (
|
|
1830
2000
|
'<article class="msg bot live" data-bot-id="' +
|
|
1831
2001
|
escapeHtml(bot.id) +
|
|
1832
|
-
'"
|
|
2002
|
+
'"' +
|
|
2003
|
+
(live.paused ? ' data-paused="1"' : "") +
|
|
2004
|
+
' role="status">' +
|
|
1833
2005
|
actor.av +
|
|
1834
2006
|
'<div class="msg-main">' +
|
|
1835
2007
|
actor.head +
|
|
@@ -1866,6 +2038,11 @@
|
|
|
1866
2038
|
const block = article.querySelector(".turn-live");
|
|
1867
2039
|
if (!block) return;
|
|
1868
2040
|
const status = block.querySelector(".turn-status");
|
|
2041
|
+
const paused = Boolean(live.paused);
|
|
2042
|
+
if ((article.getAttribute("data-paused") === "1") !== paused) {
|
|
2043
|
+
renderThread();
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
1869
2046
|
if (status) {
|
|
1870
2047
|
const started = live.startedAt ? Date.parse(live.startedAt) : NaN;
|
|
1871
2048
|
const elapsed = Number.isFinite(started)
|
|
@@ -1875,7 +2052,7 @@
|
|
|
1875
2052
|
elapsed >= 15000
|
|
1876
2053
|
? '<span class="clock">' + Math.floor(elapsed / 1000) + "s</span>"
|
|
1877
2054
|
: "";
|
|
1878
|
-
status.innerHTML = t("deepDiving") + clock;
|
|
2055
|
+
status.innerHTML = (paused ? t("live.paused") : t("deepDiving")) + clock;
|
|
1879
2056
|
}
|
|
1880
2057
|
const html = liveStepsHtml(live.steps);
|
|
1881
2058
|
const existing = block.querySelector(".live-steps");
|
|
@@ -2064,6 +2241,7 @@
|
|
|
2064
2241
|
syncThreadChrome();
|
|
2065
2242
|
return;
|
|
2066
2243
|
}
|
|
2244
|
+
const keptHtml = holdHtmlFrames(root);
|
|
2067
2245
|
root.innerHTML =
|
|
2068
2246
|
rows
|
|
2069
2247
|
.map((msg, index) => {
|
|
@@ -2219,6 +2397,7 @@
|
|
|
2219
2397
|
root.scrollTop = Math.max(0, root.scrollHeight - fromBottom);
|
|
2220
2398
|
}
|
|
2221
2399
|
syncThreadChrome();
|
|
2400
|
+
putHtmlFrames(root, keptHtml);
|
|
2222
2401
|
hydrateHtmlPreviews(root);
|
|
2223
2402
|
}
|
|
2224
2403
|
function hydrateHtmlPreviews(root) {
|
|
@@ -2226,15 +2405,16 @@
|
|
|
2226
2405
|
root.querySelectorAll(".md-html-preview").forEach((box) => {
|
|
2227
2406
|
const src = box.querySelector(".md-html-src");
|
|
2228
2407
|
const frame = box.querySelector(".md-html-frame");
|
|
2229
|
-
if (!src || !frame
|
|
2230
|
-
frame.dataset.ready = "1";
|
|
2408
|
+
if (!src || !frame) return;
|
|
2231
2409
|
const raw = src.value;
|
|
2232
2410
|
const lang = (box.querySelector(".md-fence-lang") || {}).textContent || "";
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
raw
|
|
2236
|
-
|
|
2237
|
-
|
|
2411
|
+
const doc =
|
|
2412
|
+
typeof htmlPreviewSrcdoc === "function"
|
|
2413
|
+
? htmlPreviewSrcdoc(raw, lang)
|
|
2414
|
+
: raw;
|
|
2415
|
+
if (frame.dataset.ready && frame.srcdoc === doc) return;
|
|
2416
|
+
frame.dataset.ready = "1";
|
|
2417
|
+
frame.srcdoc = doc;
|
|
2238
2418
|
});
|
|
2239
2419
|
}
|
|
2240
2420
|
function openHtmlZoom(raw) {
|
|
@@ -2262,6 +2442,7 @@
|
|
|
2262
2442
|
renderNav();
|
|
2263
2443
|
renderHead();
|
|
2264
2444
|
setHash();
|
|
2445
|
+
loadNavCron().catch(() => {});
|
|
2265
2446
|
}
|
|
2266
2447
|
async function deleteChannel(id, name) {
|
|
2267
2448
|
if (!id || id === "channel-general") {
|
|
@@ -2328,6 +2509,18 @@
|
|
|
2328
2509
|
? "/dms/" + encodeURIComponent(r.id) + "/abort"
|
|
2329
2510
|
: "/channels/" + encodeURIComponent(r.id) + "/abort";
|
|
2330
2511
|
}
|
|
2512
|
+
function pauseUrl(room) {
|
|
2513
|
+
const r = room || state.busyRoom || { kind: state.kind, id: state.id };
|
|
2514
|
+
return r.kind === "dm"
|
|
2515
|
+
? "/dms/" + encodeURIComponent(r.id) + "/pause"
|
|
2516
|
+
: "/channels/" + encodeURIComponent(r.id) + "/pause";
|
|
2517
|
+
}
|
|
2518
|
+
function continueUrl(room) {
|
|
2519
|
+
const r = room || state.busyRoom || { kind: state.kind, id: state.id };
|
|
2520
|
+
return r.kind === "dm"
|
|
2521
|
+
? "/dms/" + encodeURIComponent(r.id) + "/continue"
|
|
2522
|
+
: "/channels/" + encodeURIComponent(r.id) + "/continue";
|
|
2523
|
+
}
|
|
2331
2524
|
function stopTurn(botId) {
|
|
2332
2525
|
const room = currentRoomRef();
|
|
2333
2526
|
(state.aborts || []).forEach((item) => {
|
|
@@ -2353,6 +2546,30 @@
|
|
|
2353
2546
|
}).catch(() => {});
|
|
2354
2547
|
setBusy(false, botId ? [botId] : [], { room: room });
|
|
2355
2548
|
}
|
|
2549
|
+
function pauseTurn(botId) {
|
|
2550
|
+
const room = currentRoomRef();
|
|
2551
|
+
fetch(pauseUrl(room), {
|
|
2552
|
+
method: "POST",
|
|
2553
|
+
headers: { "content-type": "application/json" },
|
|
2554
|
+
body: JSON.stringify(botId ? { botId: botId } : {}),
|
|
2555
|
+
}).catch(() => {});
|
|
2556
|
+
(state.lives || []).forEach((row) => {
|
|
2557
|
+
if (!botId || row.botId === botId) row.paused = true;
|
|
2558
|
+
});
|
|
2559
|
+
if (state.live && (!botId || state.live.botId === botId)) {
|
|
2560
|
+
state.live.paused = true;
|
|
2561
|
+
}
|
|
2562
|
+
renderThread();
|
|
2563
|
+
}
|
|
2564
|
+
async function continueTurn(botId) {
|
|
2565
|
+
const room = currentRoomRef();
|
|
2566
|
+
(state.lives || []).forEach((row) => {
|
|
2567
|
+
if (row.botId === botId) row.paused = false;
|
|
2568
|
+
});
|
|
2569
|
+
if (state.live && state.live.botId === botId) state.live.paused = false;
|
|
2570
|
+
renderThread();
|
|
2571
|
+
await postChat(continueUrl(room), { botId: botId }, room, [botId]);
|
|
2572
|
+
}
|
|
2356
2573
|
async function insertIntoBotTurn(botId) {
|
|
2357
2574
|
if (!composerHasPayload()) {
|
|
2358
2575
|
const draft = document.getElementById("draft");
|
|
@@ -2534,8 +2751,11 @@
|
|
|
2534
2751
|
const pending = (state.messages || []).filter(
|
|
2535
2752
|
(msg) => String(msg.id).indexOf("pending-") === 0,
|
|
2536
2753
|
);
|
|
2537
|
-
if (!extras.length && !pending.length) return;
|
|
2538
2754
|
const serverYou = incoming.filter((msg) => msg.author === "you");
|
|
2755
|
+
const dropPending = pending.filter((msg) =>
|
|
2756
|
+
serverYou.some((row) => row.body === msg.body),
|
|
2757
|
+
);
|
|
2758
|
+
if (!extras.length && !dropPending.length) return;
|
|
2539
2759
|
state.messages = (state.messages || [])
|
|
2540
2760
|
.filter((msg) => {
|
|
2541
2761
|
if (String(msg.id).indexOf("pending-") === 0) {
|
|
@@ -2817,6 +3037,22 @@
|
|
|
2817
3037
|
stopTurn(liveStop.getAttribute("data-live-stop"));
|
|
2818
3038
|
return;
|
|
2819
3039
|
}
|
|
3040
|
+
const livePause = event.target.closest("[data-live-pause]");
|
|
3041
|
+
if (livePause) {
|
|
3042
|
+
event.preventDefault();
|
|
3043
|
+
event.stopImmediatePropagation();
|
|
3044
|
+
pauseTurn(livePause.getAttribute("data-live-pause"));
|
|
3045
|
+
return;
|
|
3046
|
+
}
|
|
3047
|
+
const liveContinue = event.target.closest("[data-live-continue]");
|
|
3048
|
+
if (liveContinue) {
|
|
3049
|
+
event.preventDefault();
|
|
3050
|
+
event.stopImmediatePropagation();
|
|
3051
|
+
continueTurn(liveContinue.getAttribute("data-live-continue")).catch(
|
|
3052
|
+
(err) => alert(err.message),
|
|
3053
|
+
);
|
|
3054
|
+
return;
|
|
3055
|
+
}
|
|
2820
3056
|
const liveSteer = event.target.closest("[data-live-steer]");
|
|
2821
3057
|
if (liveSteer) {
|
|
2822
3058
|
event.preventDefault();
|
|
@@ -2925,6 +3161,19 @@
|
|
|
2925
3161
|
}
|
|
2926
3162
|
});
|
|
2927
3163
|
document.getElementById("filter").addEventListener("input", renderNav);
|
|
3164
|
+
document.getElementById("nav-drawer").addEventListener("click", (event) => {
|
|
3165
|
+
const btn = event.target.closest("[data-fold]");
|
|
3166
|
+
if (!btn || event.target.closest(".add-ch")) return;
|
|
3167
|
+
event.preventDefault();
|
|
3168
|
+
const sec = btn.getAttribute("data-fold");
|
|
3169
|
+
if (!sec) return;
|
|
3170
|
+
state.navFold[sec] = !state.navFold[sec];
|
|
3171
|
+
saveNavFold();
|
|
3172
|
+
applyNavFold();
|
|
3173
|
+
if (sec === "cron" && !state.navFold.cron) {
|
|
3174
|
+
loadNavCron().catch(() => {});
|
|
3175
|
+
}
|
|
3176
|
+
});
|
|
2928
3177
|
document.getElementById("toggle-create").addEventListener("click", () => {
|
|
2929
3178
|
document.getElementById("create-row").classList.toggle("open");
|
|
2930
3179
|
document.getElementById("new-channel").focus();
|
|
@@ -3084,7 +3333,12 @@
|
|
|
3084
3333
|
if (bot) showBotCard(bot, event.currentTarget);
|
|
3085
3334
|
});
|
|
3086
3335
|
document.getElementById("thread").addEventListener("click", (event) => {
|
|
3087
|
-
if (
|
|
3336
|
+
if (
|
|
3337
|
+
event.target.closest(
|
|
3338
|
+
"[data-live-stop], [data-live-pause], [data-live-continue], [data-live-steer]",
|
|
3339
|
+
)
|
|
3340
|
+
)
|
|
3341
|
+
return;
|
|
3088
3342
|
const av = event.target.closest(
|
|
3089
3343
|
".avatar[data-bot-id], button.name[data-bot-id]",
|
|
3090
3344
|
);
|
|
@@ -3184,6 +3438,64 @@
|
|
|
3184
3438
|
channelMdDialog.close();
|
|
3185
3439
|
await loadWorkspace();
|
|
3186
3440
|
});
|
|
3441
|
+
document.getElementById("cron-cancel").addEventListener("click", () => {
|
|
3442
|
+
document.getElementById("cron-dialog").close();
|
|
3443
|
+
});
|
|
3444
|
+
document.getElementById("cron-add").addEventListener("click", async () => {
|
|
3445
|
+
const botId = document.getElementById("cron-bot").value;
|
|
3446
|
+
const ask = document.getElementById("cron-ask").value.trim();
|
|
3447
|
+
const bot = botById(botId);
|
|
3448
|
+
if (!bot) {
|
|
3449
|
+
alert(t("cron.needSeat"));
|
|
3450
|
+
return;
|
|
3451
|
+
}
|
|
3452
|
+
if (!ask) {
|
|
3453
|
+
alert(t("cron.needAsk"));
|
|
3454
|
+
return;
|
|
3455
|
+
}
|
|
3456
|
+
const raw = "@" + bot.handle + " " + t("cron.askBody", { ask: ask });
|
|
3457
|
+
document.getElementById("cron-ask").value = "";
|
|
3458
|
+
document.getElementById("cron-dialog").close();
|
|
3459
|
+
const split = splitSendTargets([botId]);
|
|
3460
|
+
if (split.fresh.length) {
|
|
3461
|
+
await flushSend(raw, [], "", [botId], botId);
|
|
3462
|
+
} else {
|
|
3463
|
+
enqueuePending({
|
|
3464
|
+
raw: raw,
|
|
3465
|
+
packed: [],
|
|
3466
|
+
replyTo: "",
|
|
3467
|
+
botIds: [botId],
|
|
3468
|
+
assigneeId: botId,
|
|
3469
|
+
});
|
|
3470
|
+
}
|
|
3471
|
+
});
|
|
3472
|
+
document.getElementById("cron-list").addEventListener("click", async (event) => {
|
|
3473
|
+
const btn = event.target.closest("[data-cron-act]");
|
|
3474
|
+
const row = event.target.closest("[data-cron-id]");
|
|
3475
|
+
if (!btn || !row) return;
|
|
3476
|
+
const id = row.getAttribute("data-cron-id");
|
|
3477
|
+
const act = btn.getAttribute("data-cron-act");
|
|
3478
|
+
if (act === "remove") {
|
|
3479
|
+
const res = await fetch("/cron/" + encodeURIComponent(id), { method: "DELETE" });
|
|
3480
|
+
if (!res.ok) {
|
|
3481
|
+
const body = await res.json().catch(() => ({}));
|
|
3482
|
+
alert(body.error || "cron failed");
|
|
3483
|
+
return;
|
|
3484
|
+
}
|
|
3485
|
+
} else {
|
|
3486
|
+
const res = await fetch(
|
|
3487
|
+
"/cron/" + encodeURIComponent(id) + "/" + act,
|
|
3488
|
+
{ method: "POST" },
|
|
3489
|
+
);
|
|
3490
|
+
const body = await res.json().catch(() => ({}));
|
|
3491
|
+
if (!res.ok) {
|
|
3492
|
+
alert(body.error || "cron failed");
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
await renderCronList();
|
|
3497
|
+
loadNavCron().catch(() => {});
|
|
3498
|
+
});
|
|
3187
3499
|
document.getElementById("bot-memory-cancel").addEventListener("click", () => {
|
|
3188
3500
|
botMemoryDialog.close();
|
|
3189
3501
|
});
|
|
@@ -4219,6 +4531,85 @@
|
|
|
4219
4531
|
body: chunks.join("\n\n") || t("attach.noPersona"),
|
|
4220
4532
|
});
|
|
4221
4533
|
}
|
|
4534
|
+
function cronRoomId() {
|
|
4535
|
+
if (state.kind === "dm") return "dm-" + state.id;
|
|
4536
|
+
return state.id;
|
|
4537
|
+
}
|
|
4538
|
+
async function loadCronJobs() {
|
|
4539
|
+
const id = cronRoomId();
|
|
4540
|
+
if (!id) return [];
|
|
4541
|
+
const res = await fetch("/cron?room=" + encodeURIComponent(id));
|
|
4542
|
+
const body = await res.json().catch(() => ({}));
|
|
4543
|
+
return Array.isArray(body.jobs) ? body.jobs : [];
|
|
4544
|
+
}
|
|
4545
|
+
function cronWhen(iso) {
|
|
4546
|
+
const d = new Date(iso || "");
|
|
4547
|
+
if (!Number.isFinite(d.getTime())) return String(iso || "");
|
|
4548
|
+
return d.toLocaleString();
|
|
4549
|
+
}
|
|
4550
|
+
async function renderCronList() {
|
|
4551
|
+
const box = document.getElementById("cron-list");
|
|
4552
|
+
const jobs = await loadCronJobs();
|
|
4553
|
+
if (!jobs.length) {
|
|
4554
|
+
box.innerHTML = '<div class="cron-empty">' + t("cron.empty") + "</div>";
|
|
4555
|
+
return;
|
|
4556
|
+
}
|
|
4557
|
+
box.innerHTML = jobs
|
|
4558
|
+
.map((job) => {
|
|
4559
|
+
const pauseLabel = job.paused ? t("cron.resume") : t("cron.pause");
|
|
4560
|
+
const pauseAct = job.paused ? "resume" : "pause";
|
|
4561
|
+
const err =
|
|
4562
|
+
job.lastStatus === "failed" && job.lastError
|
|
4563
|
+
? " · " + escapeHtml(job.lastError)
|
|
4564
|
+
: "";
|
|
4565
|
+
return (
|
|
4566
|
+
'<div class="cron-row" data-cron-id="' +
|
|
4567
|
+
escapeHtml(job.id) +
|
|
4568
|
+
'"><div><b>' +
|
|
4569
|
+
escapeHtml(job.name) +
|
|
4570
|
+
"</b><span>" +
|
|
4571
|
+
escapeHtml(job.schedule) +
|
|
4572
|
+
" · " +
|
|
4573
|
+
t("cron.next", { when: cronWhen(job.nextRunAt) }) +
|
|
4574
|
+
(job.paused ? " · paused" : "") +
|
|
4575
|
+
err +
|
|
4576
|
+
'</span></div><div class="cron-acts">' +
|
|
4577
|
+
'<button type="button" data-cron-act="' +
|
|
4578
|
+
pauseAct +
|
|
4579
|
+
'">' +
|
|
4580
|
+
pauseLabel +
|
|
4581
|
+
'</button><button type="button" data-cron-act="run">' +
|
|
4582
|
+
t("cron.run") +
|
|
4583
|
+
'</button><button type="button" class="ghost danger" data-cron-act="remove">' +
|
|
4584
|
+
t("cron.remove") +
|
|
4585
|
+
"</button></div></div>"
|
|
4586
|
+
);
|
|
4587
|
+
})
|
|
4588
|
+
.join("");
|
|
4589
|
+
}
|
|
4590
|
+
async function openCronDialog() {
|
|
4591
|
+
const dialog = document.getElementById("cron-dialog");
|
|
4592
|
+
const sel = document.getElementById("cron-bot");
|
|
4593
|
+
const ch = currentChannel();
|
|
4594
|
+
const members = ch ? ch.members || [] : [];
|
|
4595
|
+
const dmBot =
|
|
4596
|
+
state.kind === "dm" && state.id ? botById(state.id) : null;
|
|
4597
|
+
const bots = members.length ? members : dmBot ? [dmBot] : [];
|
|
4598
|
+
sel.innerHTML = bots
|
|
4599
|
+
.map(
|
|
4600
|
+
(bot) =>
|
|
4601
|
+
'<option value="' +
|
|
4602
|
+
escapeHtml(bot.id) +
|
|
4603
|
+
'">@' +
|
|
4604
|
+
escapeHtml(bot.handle) +
|
|
4605
|
+
"</option>",
|
|
4606
|
+
)
|
|
4607
|
+
.join("");
|
|
4608
|
+
await renderCronList();
|
|
4609
|
+
dialog.showModal();
|
|
4610
|
+
const ask = document.getElementById("cron-ask");
|
|
4611
|
+
if (ask) ask.focus();
|
|
4612
|
+
}
|
|
4222
4613
|
function openSlashSheet() {
|
|
4223
4614
|
const rows = [
|
|
4224
4615
|
{ id: "channel-md", label: t("slash.editMd"), hint: t("slash.editMdHint") },
|
|
@@ -4226,6 +4617,7 @@
|
|
|
4226
4617
|
{ id: "git", label: t("slash.git"), hint: "status + diff" },
|
|
4227
4618
|
{ id: "here", label: t("slash.here"), hint: t("slash.hereHint") },
|
|
4228
4619
|
{ id: "library", label: t("slash.library"), hint: "/library" },
|
|
4620
|
+
{ id: "cron", label: t("slash.cron"), hint: t("slash.cronHint") },
|
|
4229
4621
|
];
|
|
4230
4622
|
if (state.kind !== "channel") {
|
|
4231
4623
|
rows.splice(0, 1);
|
|
@@ -4255,6 +4647,11 @@
|
|
|
4255
4647
|
}
|
|
4256
4648
|
if (id === "library") {
|
|
4257
4649
|
window.location.href = "/library";
|
|
4650
|
+
return;
|
|
4651
|
+
}
|
|
4652
|
+
if (id === "cron") {
|
|
4653
|
+
hideAttach();
|
|
4654
|
+
openCronDialog();
|
|
4258
4655
|
}
|
|
4259
4656
|
}
|
|
4260
4657
|
async function runAttach(kind) {
|
|
@@ -4276,6 +4673,10 @@
|
|
|
4276
4673
|
renderAttachSheet();
|
|
4277
4674
|
return;
|
|
4278
4675
|
}
|
|
4676
|
+
if (kind === "cron") {
|
|
4677
|
+
hideAttach();
|
|
4678
|
+
return openCronDialog();
|
|
4679
|
+
}
|
|
4279
4680
|
if (kind === "slash") return openSlashSheet();
|
|
4280
4681
|
}
|
|
4281
4682
|
function startLiveTimers() {
|
|
@@ -4452,6 +4853,9 @@
|
|
|
4452
4853
|
}
|
|
4453
4854
|
setBusy(false, botIds, { room: sent });
|
|
4454
4855
|
}
|
|
4856
|
+
if (state.kind === sent.kind && state.id === sent.id) {
|
|
4857
|
+
await resumeCurrentLive().catch(() => {});
|
|
4858
|
+
}
|
|
4455
4859
|
if (ok) await flushQueued(sent);
|
|
4456
4860
|
}
|
|
4457
4861
|
async function flushQueued(sent) {
|
|
@@ -5084,6 +5488,15 @@
|
|
|
5084
5488
|
hint: "/library",
|
|
5085
5489
|
glyph: "/",
|
|
5086
5490
|
},
|
|
5491
|
+
{
|
|
5492
|
+
kind: "cmd",
|
|
5493
|
+
section: "cmd",
|
|
5494
|
+
cmdId: "cron",
|
|
5495
|
+
insert: "",
|
|
5496
|
+
name: t("slash.cron"),
|
|
5497
|
+
hint: t("slash.cronHint"),
|
|
5498
|
+
glyph: "/",
|
|
5499
|
+
},
|
|
5087
5500
|
];
|
|
5088
5501
|
if (state.kind !== "channel") {
|
|
5089
5502
|
return rows.filter(
|
|
@@ -5550,8 +5963,9 @@
|
|
|
5550
5963
|
document.getElementById("model-fast").checked = Boolean(modelCfg.fast);
|
|
5551
5964
|
const spec = activeReasoningSpec();
|
|
5552
5965
|
const choices = effortChoices(spec);
|
|
5966
|
+
const currentRef = activeModelRef();
|
|
5553
5967
|
const reasonVal = clampEffort(
|
|
5554
|
-
|
|
5968
|
+
currentRef && currentRef.reasoning,
|
|
5555
5969
|
spec,
|
|
5556
5970
|
Boolean(modelCfg.fast),
|
|
5557
5971
|
);
|
|
@@ -5621,11 +6035,24 @@
|
|
|
5621
6035
|
async function applyChatModel(ref) {
|
|
5622
6036
|
if (state.kind !== "dm") return;
|
|
5623
6037
|
const bot = botById(state.id);
|
|
5624
|
-
if (!bot) return;
|
|
6038
|
+
if (!bot || !ref || !ref.provider || !ref.model) return;
|
|
6039
|
+
const spec = (() => {
|
|
6040
|
+
const hit = allModels().find(
|
|
6041
|
+
(row) => row.provider === ref.provider && row.model === ref.model,
|
|
6042
|
+
);
|
|
6043
|
+
return (hit && hit.reasoning) || null;
|
|
6044
|
+
})();
|
|
6045
|
+
const reason = clampEffort(
|
|
6046
|
+
document.getElementById("model-reasoning").value || ref.reasoning,
|
|
6047
|
+
spec,
|
|
6048
|
+
Boolean(document.getElementById("model-fast").checked),
|
|
6049
|
+
);
|
|
6050
|
+
const next = { provider: ref.provider, model: ref.model };
|
|
6051
|
+
if (reason) next.reasoning = reason;
|
|
5625
6052
|
const res = await fetch("/bots/" + encodeURIComponent(bot.id), {
|
|
5626
6053
|
method: "PATCH",
|
|
5627
6054
|
headers: { "content-type": "application/json" },
|
|
5628
|
-
body: JSON.stringify({ model:
|
|
6055
|
+
body: JSON.stringify({ model: next }),
|
|
5629
6056
|
});
|
|
5630
6057
|
if (!res.ok) {
|
|
5631
6058
|
const body = await res.json().catch(() => ({}));
|
|
@@ -5635,16 +6062,16 @@
|
|
|
5635
6062
|
const updated = await res.json();
|
|
5636
6063
|
const i = state.bots.findIndex((item) => item.id === bot.id);
|
|
5637
6064
|
if (i >= 0) state.bots[i] = { ...state.bots[i], model: updated.model };
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
})
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
}
|
|
6065
|
+
const fast = document.getElementById("model-fast").checked;
|
|
6066
|
+
if (Boolean(modelCfg.fast) !== Boolean(fast)) {
|
|
6067
|
+
await fetch("/settings/models", {
|
|
6068
|
+
method: "PUT",
|
|
6069
|
+
headers: { "content-type": "application/json" },
|
|
6070
|
+
body: JSON.stringify({ fast: fast }),
|
|
6071
|
+
}).then(async (r) => {
|
|
6072
|
+
if (r.ok) modelCfg = await r.json();
|
|
6073
|
+
});
|
|
6074
|
+
}
|
|
5648
6075
|
renderModelList();
|
|
5649
6076
|
renderNav();
|
|
5650
6077
|
}
|
|
@@ -5967,6 +6394,11 @@
|
|
|
5967
6394
|
setNavOpen(false);
|
|
5968
6395
|
openFromHash().catch((err) => alert(err.message));
|
|
5969
6396
|
});
|
|
6397
|
+
applyNavFold();
|
|
6398
|
+
setInterval(() => {
|
|
6399
|
+
if (document.hidden || navSecFolded("cron")) return;
|
|
6400
|
+
loadNavCron().catch(() => {});
|
|
6401
|
+
}, 15000);
|
|
5970
6402
|
Promise.all([openFromHash(), loadModelPick()])
|
|
5971
6403
|
.then(() => loadSlashCatalog().catch(() => {}))
|
|
5972
6404
|
.catch((err) => {
|