@kevin5251984/guild 0.2.20 → 0.2.21
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/package.json +1 -1
- package/src/handlers.ts +97 -5
- package/src/llm.ts +1 -1
- package/src/public/chat.css +6 -0
- package/src/public/chat.html +121 -24
- package/src/public/i18n.js +3 -0
- package/src/public/md.js +18 -1
- package/src/public/mobile.css +105 -1
- package/src/public/mobile.html +163 -11
- package/src/router.ts +64 -1
- package/src/store.ts +94 -32
- package/vendor/protocol/src/index.ts +7 -2
package/package.json
CHANGED
package/src/handlers.ts
CHANGED
|
@@ -522,6 +522,7 @@ export function workspace(store: GuildStore) {
|
|
|
522
522
|
startedAt: turn.startedAt || "",
|
|
523
523
|
thinking: turn.thinking,
|
|
524
524
|
steps: turn.steps,
|
|
525
|
+
...(turn.paused ? { paused: true } : {}),
|
|
525
526
|
},
|
|
526
527
|
];
|
|
527
528
|
});
|
|
@@ -996,6 +997,7 @@ function publicLiveTurn(live: LiveTurn): LiveTurn {
|
|
|
996
997
|
thinking: live.thinking,
|
|
997
998
|
steps: live.steps,
|
|
998
999
|
startedAt: live.startedAt,
|
|
1000
|
+
...(live.paused ? { paused: true } : {}),
|
|
999
1001
|
};
|
|
1000
1002
|
}
|
|
1001
1003
|
|
|
@@ -1085,6 +1087,84 @@ export function abortLiveTurn(
|
|
|
1085
1087
|
return { ok: true };
|
|
1086
1088
|
}
|
|
1087
1089
|
|
|
1090
|
+
export function pauseLiveTurn(
|
|
1091
|
+
store: GuildStore,
|
|
1092
|
+
roomId: string,
|
|
1093
|
+
botId?: string,
|
|
1094
|
+
) {
|
|
1095
|
+
if (!store.getRoom(roomId)) throw new StoreError(404, "room not found");
|
|
1096
|
+
const live = botId
|
|
1097
|
+
? store.getLiveBotTurn(roomId, botId)
|
|
1098
|
+
: store.getLiveTurn(roomId);
|
|
1099
|
+
if (live?.paused) return { ok: true, paused: true as const };
|
|
1100
|
+
const had = store.pauseTurn(roomId, botId);
|
|
1101
|
+
if (!live && !had) throw new StoreError(409, "no live turn");
|
|
1102
|
+
return { ok: true, paused: true as const };
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
function resumeSteer(live: LiveTurn): string {
|
|
1106
|
+
const lines = [
|
|
1107
|
+
"Paused mid-turn so the user could switch models. Continue from here. Do not redo finished tools unless you need a different result.",
|
|
1108
|
+
];
|
|
1109
|
+
const thinking = (live.thinking || "").trim();
|
|
1110
|
+
if (thinking) lines.push(`Thinking so far:\n${thinking.slice(0, 6000)}`);
|
|
1111
|
+
const tools = (live.traces || []).filter((tr) => tr.name && tr.name !== "think");
|
|
1112
|
+
if (tools.length) {
|
|
1113
|
+
lines.push(
|
|
1114
|
+
"Tools already run:\n" +
|
|
1115
|
+
tools
|
|
1116
|
+
.slice(-20)
|
|
1117
|
+
.map((tr) => {
|
|
1118
|
+
const bit = String(tr.text || "")
|
|
1119
|
+
.replace(/\s+/g, " ")
|
|
1120
|
+
.trim()
|
|
1121
|
+
.slice(0, 400);
|
|
1122
|
+
return `- ${tr.name}${bit ? `: ${bit}` : ""}`;
|
|
1123
|
+
})
|
|
1124
|
+
.join("\n"),
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
return lines.join("\n\n");
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
export async function continueLiveTurn(
|
|
1131
|
+
store: GuildStore,
|
|
1132
|
+
roomId: string,
|
|
1133
|
+
botId: string,
|
|
1134
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
1135
|
+
extras: HandlerExtras = {},
|
|
1136
|
+
) {
|
|
1137
|
+
if (!store.getRoom(roomId)) throw new StoreError(404, "room not found");
|
|
1138
|
+
const id = botId.trim();
|
|
1139
|
+
if (!id) throw new StoreError(400, "botId is required");
|
|
1140
|
+
const live = store.getLiveBotTurn(roomId, id);
|
|
1141
|
+
if (!live?.paused) throw new StoreError(409, "no paused turn");
|
|
1142
|
+
const messages = store.listMessages(roomId);
|
|
1143
|
+
let userIndex = messages.length - 1;
|
|
1144
|
+
while (userIndex >= 0 && messages[userIndex].author !== "you") userIndex -= 1;
|
|
1145
|
+
if (userIndex < 0) throw new StoreError(400, "no user message to continue");
|
|
1146
|
+
const userMessage = messages[userIndex];
|
|
1147
|
+
const asked = live.asked?.trim() || userMessage.body.trim();
|
|
1148
|
+
if (!asked) throw new StoreError(409, "nothing to continue");
|
|
1149
|
+
const history = messages.slice(0, userIndex).map(toHistoryItem);
|
|
1150
|
+
const parent = parentMessage(messages.slice(0, userIndex), userMessage.replyTo);
|
|
1151
|
+
const room = store.getRoom(roomId);
|
|
1152
|
+
store.setLiveTurn(roomId, { ...live, paused: false });
|
|
1153
|
+
store.pushSteer(roomId, resumeSteer(live), id);
|
|
1154
|
+
const replies = await generateReplies(
|
|
1155
|
+
store,
|
|
1156
|
+
roomId,
|
|
1157
|
+
room?.memberIds ?? [id],
|
|
1158
|
+
{ ...userMessage, body: asked },
|
|
1159
|
+
history,
|
|
1160
|
+
id,
|
|
1161
|
+
env,
|
|
1162
|
+
parent,
|
|
1163
|
+
extras,
|
|
1164
|
+
);
|
|
1165
|
+
return { replies };
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1088
1168
|
function isAbortError(err: unknown): boolean {
|
|
1089
1169
|
return Boolean(
|
|
1090
1170
|
err &&
|
|
@@ -1169,14 +1249,19 @@ async function generateReplies(
|
|
|
1169
1249
|
if (!memberIds.includes(botId)) return;
|
|
1170
1250
|
if (signal.aborted) return;
|
|
1171
1251
|
const prev = store.getLiveBotTurn(roomId, botId);
|
|
1252
|
+
if (!prev || prev.paused) return;
|
|
1172
1253
|
const startedAt = prev?.startedAt || new Date().toISOString();
|
|
1173
1254
|
store.dropLastFailedReply(roomId, botId);
|
|
1174
1255
|
store.setLiveTurn(roomId, {
|
|
1175
1256
|
botId,
|
|
1176
1257
|
thinking: prev?.thinking || "",
|
|
1177
1258
|
steps: prev?.steps || [],
|
|
1259
|
+
traces: prev?.traces,
|
|
1260
|
+
asked: turnAsked,
|
|
1178
1261
|
startedAt,
|
|
1262
|
+
paused: false,
|
|
1179
1263
|
});
|
|
1264
|
+
const botSignal = store.armBotTurn(roomId, botId, signal);
|
|
1180
1265
|
let generated;
|
|
1181
1266
|
try {
|
|
1182
1267
|
generated = await (extras.turn ?? chatReply)({
|
|
@@ -1189,10 +1274,11 @@ async function generateReplies(
|
|
|
1189
1274
|
userMessage.body,
|
|
1190
1275
|
),
|
|
1191
1276
|
env,
|
|
1192
|
-
signal,
|
|
1277
|
+
signal: botSignal,
|
|
1193
1278
|
mcpTools,
|
|
1194
1279
|
onProgress: (update) => {
|
|
1195
1280
|
const prev = store.getLiveBotTurn(roomId, botId);
|
|
1281
|
+
if (prev?.paused) return;
|
|
1196
1282
|
const next = toLiveTurn(botId, update);
|
|
1197
1283
|
const handoff = (prev?.steps || []).find((step) => step.name === "handoff");
|
|
1198
1284
|
const pendingSteers = store.peekSteers(roomId, botId).map((text) => ({
|
|
@@ -1209,14 +1295,16 @@ async function generateReplies(
|
|
|
1209
1295
|
);
|
|
1210
1296
|
store.setLiveTurn(roomId, {
|
|
1211
1297
|
...next,
|
|
1298
|
+
asked: prev?.asked || turnAsked,
|
|
1212
1299
|
startedAt: prev?.startedAt || startedAt,
|
|
1300
|
+
paused: false,
|
|
1213
1301
|
steps: [...(handoff ? [handoff] : []), ...keptSteer, ...rest].slice(0, 5),
|
|
1214
1302
|
});
|
|
1215
1303
|
},
|
|
1216
1304
|
pullSteers: () => store.drainSteers(roomId, botId),
|
|
1217
1305
|
});
|
|
1218
1306
|
} catch (err) {
|
|
1219
|
-
if (isAbortError(err) || signal.aborted) return;
|
|
1307
|
+
if (isAbortError(err) || botSignal.aborted || signal.aborted) return;
|
|
1220
1308
|
throw err;
|
|
1221
1309
|
}
|
|
1222
1310
|
const usage = { ...(generated.usage || {}), startedAt };
|
|
@@ -1406,11 +1494,15 @@ function plantLiveTurns(
|
|
|
1406
1494
|
if (!botId) continue;
|
|
1407
1495
|
if (!memberIds.includes(botId)) continue;
|
|
1408
1496
|
store.dropLastFailedReply(roomId, botId);
|
|
1497
|
+
const prev = store.getLiveBotTurn(roomId, botId);
|
|
1409
1498
|
store.setLiveTurn(roomId, {
|
|
1410
1499
|
botId,
|
|
1411
|
-
thinking: "",
|
|
1412
|
-
steps: [],
|
|
1413
|
-
|
|
1500
|
+
thinking: prev?.thinking || "",
|
|
1501
|
+
steps: prev?.steps || [],
|
|
1502
|
+
traces: prev?.traces,
|
|
1503
|
+
asked: prev?.asked,
|
|
1504
|
+
startedAt: prev?.startedAt || startedAt,
|
|
1505
|
+
paused: false,
|
|
1414
1506
|
});
|
|
1415
1507
|
}
|
|
1416
1508
|
return startedAt;
|
package/src/llm.ts
CHANGED
|
@@ -496,7 +496,7 @@ export async function llmComplete(input: {
|
|
|
496
496
|
};
|
|
497
497
|
const file = readModelsFile(input.dataDir);
|
|
498
498
|
const effort = clampEffort(
|
|
499
|
-
file.fast ? "low" : file.reasoning,
|
|
499
|
+
file.fast ? "low" : input.prefer ? input.prefer.reasoning : file.reasoning,
|
|
500
500
|
reasoningFor(target.providerId, target.model),
|
|
501
501
|
Boolean(file.fast),
|
|
502
502
|
);
|
package/src/public/chat.css
CHANGED
|
@@ -1894,6 +1894,10 @@ a.invite-btn:hover { text-decoration: none; }
|
|
|
1894
1894
|
.turn-actions .turn-stop {
|
|
1895
1895
|
color: var(--danger);
|
|
1896
1896
|
}
|
|
1897
|
+
.turn-actions .turn-pause,
|
|
1898
|
+
.turn-actions .turn-continue {
|
|
1899
|
+
color: var(--steel);
|
|
1900
|
+
}
|
|
1897
1901
|
.turn-actions .turn-steer {
|
|
1898
1902
|
color: #b4c8e8;
|
|
1899
1903
|
}
|
|
@@ -1907,6 +1911,8 @@ body.grok .turn-actions button:hover {
|
|
|
1907
1911
|
color: #f2eadf;
|
|
1908
1912
|
}
|
|
1909
1913
|
body.grok .turn-actions .turn-stop { color: #d4a090; }
|
|
1914
|
+
body.grok .turn-actions .turn-pause,
|
|
1915
|
+
body.grok .turn-actions .turn-continue { color: #c4b090; }
|
|
1910
1916
|
body.grok .turn-actions .turn-steer { color: #c4b090; }
|
|
1911
1917
|
.turn-status .clock {
|
|
1912
1918
|
margin-left: 8px;
|
package/src/public/chat.html
CHANGED
|
@@ -1292,8 +1292,8 @@
|
|
|
1292
1292
|
}
|
|
1293
1293
|
function botModelMeta(bot) {
|
|
1294
1294
|
const model = botModelLabel(bot) || "—";
|
|
1295
|
+
const ref = botModelRef(bot);
|
|
1295
1296
|
const spec = (() => {
|
|
1296
|
-
const ref = botModelRef(bot);
|
|
1297
1297
|
if (!ref) return null;
|
|
1298
1298
|
const hit = allModels().find(
|
|
1299
1299
|
(row) => row.provider === ref.provider && row.model === ref.model,
|
|
@@ -1301,7 +1301,7 @@
|
|
|
1301
1301
|
return (hit && hit.reasoning) || null;
|
|
1302
1302
|
})();
|
|
1303
1303
|
const reasonVal = clampEffort(
|
|
1304
|
-
|
|
1304
|
+
ref && ref.reasoning,
|
|
1305
1305
|
spec,
|
|
1306
1306
|
Boolean(modelCfg && modelCfg.fast),
|
|
1307
1307
|
);
|
|
@@ -1713,6 +1713,7 @@
|
|
|
1713
1713
|
thinking: row.thinking || "",
|
|
1714
1714
|
steps: Array.isArray(row.steps) ? row.steps.slice(-5) : [],
|
|
1715
1715
|
startedAt: row.startedAt || "",
|
|
1716
|
+
paused: Boolean(row.paused),
|
|
1716
1717
|
}));
|
|
1717
1718
|
}
|
|
1718
1719
|
function rebuildBusyBots() {
|
|
@@ -1741,6 +1742,7 @@
|
|
|
1741
1742
|
thinking: row.thinking || "",
|
|
1742
1743
|
steps: Array.isArray(row.steps) ? row.steps.slice(-5) : [],
|
|
1743
1744
|
startedAt: row.startedAt || "",
|
|
1745
|
+
paused: Boolean(row.paused),
|
|
1744
1746
|
room: { kind: r.kind, id: r.id },
|
|
1745
1747
|
}));
|
|
1746
1748
|
state.lives = others.concat(tagged);
|
|
@@ -1791,24 +1793,38 @@
|
|
|
1791
1793
|
? '<span class="clock">' + Math.floor(elapsed / 1000) + "s</span>"
|
|
1792
1794
|
: "";
|
|
1793
1795
|
const botId = live && live.botId ? live.botId : "";
|
|
1796
|
+
const paused = Boolean(live && live.paused);
|
|
1794
1797
|
const actions = botId
|
|
1795
1798
|
? '<div class="turn-actions">' +
|
|
1799
|
+
(paused
|
|
1800
|
+
? '<button type="button" class="turn-continue" data-live-continue="' +
|
|
1801
|
+
escapeHtml(botId) +
|
|
1802
|
+
'">' +
|
|
1803
|
+
t("live.continue") +
|
|
1804
|
+
"</button>"
|
|
1805
|
+
: '<button type="button" class="turn-pause" data-live-pause="' +
|
|
1806
|
+
escapeHtml(botId) +
|
|
1807
|
+
'">' +
|
|
1808
|
+
t("live.pause") +
|
|
1809
|
+
"</button>") +
|
|
1796
1810
|
'<button type="button" class="turn-stop" data-live-stop="' +
|
|
1797
1811
|
escapeHtml(botId) +
|
|
1798
1812
|
'">' +
|
|
1799
1813
|
t("live.stop") +
|
|
1800
1814
|
"</button>" +
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1815
|
+
(paused
|
|
1816
|
+
? ""
|
|
1817
|
+
: '<button type="button" class="turn-steer" data-live-steer="' +
|
|
1818
|
+
escapeHtml(botId) +
|
|
1819
|
+
'">' +
|
|
1820
|
+
t("live.steer") +
|
|
1821
|
+
"</button>") +
|
|
1806
1822
|
"</div>"
|
|
1807
1823
|
: "";
|
|
1808
1824
|
return (
|
|
1809
1825
|
'<div class="turn-live">' +
|
|
1810
1826
|
'<div class="turn-status">' +
|
|
1811
|
-
t("deepDiving") +
|
|
1827
|
+
(paused ? t("live.paused") : t("deepDiving")) +
|
|
1812
1828
|
clock +
|
|
1813
1829
|
"</div>" +
|
|
1814
1830
|
liveStepsHtml(live && live.steps) +
|
|
@@ -1829,7 +1845,9 @@
|
|
|
1829
1845
|
return (
|
|
1830
1846
|
'<article class="msg bot live" data-bot-id="' +
|
|
1831
1847
|
escapeHtml(bot.id) +
|
|
1832
|
-
'"
|
|
1848
|
+
'"' +
|
|
1849
|
+
(live.paused ? ' data-paused="1"' : "") +
|
|
1850
|
+
' role="status">' +
|
|
1833
1851
|
actor.av +
|
|
1834
1852
|
'<div class="msg-main">' +
|
|
1835
1853
|
actor.head +
|
|
@@ -1866,6 +1884,11 @@
|
|
|
1866
1884
|
const block = article.querySelector(".turn-live");
|
|
1867
1885
|
if (!block) return;
|
|
1868
1886
|
const status = block.querySelector(".turn-status");
|
|
1887
|
+
const paused = Boolean(live.paused);
|
|
1888
|
+
if ((article.getAttribute("data-paused") === "1") !== paused) {
|
|
1889
|
+
renderThread();
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1869
1892
|
if (status) {
|
|
1870
1893
|
const started = live.startedAt ? Date.parse(live.startedAt) : NaN;
|
|
1871
1894
|
const elapsed = Number.isFinite(started)
|
|
@@ -1875,7 +1898,7 @@
|
|
|
1875
1898
|
elapsed >= 15000
|
|
1876
1899
|
? '<span class="clock">' + Math.floor(elapsed / 1000) + "s</span>"
|
|
1877
1900
|
: "";
|
|
1878
|
-
status.innerHTML = t("deepDiving") + clock;
|
|
1901
|
+
status.innerHTML = (paused ? t("live.paused") : t("deepDiving")) + clock;
|
|
1879
1902
|
}
|
|
1880
1903
|
const html = liveStepsHtml(live.steps);
|
|
1881
1904
|
const existing = block.querySelector(".live-steps");
|
|
@@ -2328,6 +2351,18 @@
|
|
|
2328
2351
|
? "/dms/" + encodeURIComponent(r.id) + "/abort"
|
|
2329
2352
|
: "/channels/" + encodeURIComponent(r.id) + "/abort";
|
|
2330
2353
|
}
|
|
2354
|
+
function pauseUrl(room) {
|
|
2355
|
+
const r = room || state.busyRoom || { kind: state.kind, id: state.id };
|
|
2356
|
+
return r.kind === "dm"
|
|
2357
|
+
? "/dms/" + encodeURIComponent(r.id) + "/pause"
|
|
2358
|
+
: "/channels/" + encodeURIComponent(r.id) + "/pause";
|
|
2359
|
+
}
|
|
2360
|
+
function continueUrl(room) {
|
|
2361
|
+
const r = room || state.busyRoom || { kind: state.kind, id: state.id };
|
|
2362
|
+
return r.kind === "dm"
|
|
2363
|
+
? "/dms/" + encodeURIComponent(r.id) + "/continue"
|
|
2364
|
+
: "/channels/" + encodeURIComponent(r.id) + "/continue";
|
|
2365
|
+
}
|
|
2331
2366
|
function stopTurn(botId) {
|
|
2332
2367
|
const room = currentRoomRef();
|
|
2333
2368
|
(state.aborts || []).forEach((item) => {
|
|
@@ -2353,6 +2388,30 @@
|
|
|
2353
2388
|
}).catch(() => {});
|
|
2354
2389
|
setBusy(false, botId ? [botId] : [], { room: room });
|
|
2355
2390
|
}
|
|
2391
|
+
function pauseTurn(botId) {
|
|
2392
|
+
const room = currentRoomRef();
|
|
2393
|
+
fetch(pauseUrl(room), {
|
|
2394
|
+
method: "POST",
|
|
2395
|
+
headers: { "content-type": "application/json" },
|
|
2396
|
+
body: JSON.stringify(botId ? { botId: botId } : {}),
|
|
2397
|
+
}).catch(() => {});
|
|
2398
|
+
(state.lives || []).forEach((row) => {
|
|
2399
|
+
if (!botId || row.botId === botId) row.paused = true;
|
|
2400
|
+
});
|
|
2401
|
+
if (state.live && (!botId || state.live.botId === botId)) {
|
|
2402
|
+
state.live.paused = true;
|
|
2403
|
+
}
|
|
2404
|
+
renderThread();
|
|
2405
|
+
}
|
|
2406
|
+
async function continueTurn(botId) {
|
|
2407
|
+
const room = currentRoomRef();
|
|
2408
|
+
(state.lives || []).forEach((row) => {
|
|
2409
|
+
if (row.botId === botId) row.paused = false;
|
|
2410
|
+
});
|
|
2411
|
+
if (state.live && state.live.botId === botId) state.live.paused = false;
|
|
2412
|
+
renderThread();
|
|
2413
|
+
await postChat(continueUrl(room), { botId: botId }, room, [botId]);
|
|
2414
|
+
}
|
|
2356
2415
|
async function insertIntoBotTurn(botId) {
|
|
2357
2416
|
if (!composerHasPayload()) {
|
|
2358
2417
|
const draft = document.getElementById("draft");
|
|
@@ -2817,6 +2876,22 @@
|
|
|
2817
2876
|
stopTurn(liveStop.getAttribute("data-live-stop"));
|
|
2818
2877
|
return;
|
|
2819
2878
|
}
|
|
2879
|
+
const livePause = event.target.closest("[data-live-pause]");
|
|
2880
|
+
if (livePause) {
|
|
2881
|
+
event.preventDefault();
|
|
2882
|
+
event.stopImmediatePropagation();
|
|
2883
|
+
pauseTurn(livePause.getAttribute("data-live-pause"));
|
|
2884
|
+
return;
|
|
2885
|
+
}
|
|
2886
|
+
const liveContinue = event.target.closest("[data-live-continue]");
|
|
2887
|
+
if (liveContinue) {
|
|
2888
|
+
event.preventDefault();
|
|
2889
|
+
event.stopImmediatePropagation();
|
|
2890
|
+
continueTurn(liveContinue.getAttribute("data-live-continue")).catch(
|
|
2891
|
+
(err) => alert(err.message),
|
|
2892
|
+
);
|
|
2893
|
+
return;
|
|
2894
|
+
}
|
|
2820
2895
|
const liveSteer = event.target.closest("[data-live-steer]");
|
|
2821
2896
|
if (liveSteer) {
|
|
2822
2897
|
event.preventDefault();
|
|
@@ -3084,7 +3159,12 @@
|
|
|
3084
3159
|
if (bot) showBotCard(bot, event.currentTarget);
|
|
3085
3160
|
});
|
|
3086
3161
|
document.getElementById("thread").addEventListener("click", (event) => {
|
|
3087
|
-
if (
|
|
3162
|
+
if (
|
|
3163
|
+
event.target.closest(
|
|
3164
|
+
"[data-live-stop], [data-live-pause], [data-live-continue], [data-live-steer]",
|
|
3165
|
+
)
|
|
3166
|
+
)
|
|
3167
|
+
return;
|
|
3088
3168
|
const av = event.target.closest(
|
|
3089
3169
|
".avatar[data-bot-id], button.name[data-bot-id]",
|
|
3090
3170
|
);
|
|
@@ -4452,6 +4532,9 @@
|
|
|
4452
4532
|
}
|
|
4453
4533
|
setBusy(false, botIds, { room: sent });
|
|
4454
4534
|
}
|
|
4535
|
+
if (state.kind === sent.kind && state.id === sent.id) {
|
|
4536
|
+
await resumeCurrentLive().catch(() => {});
|
|
4537
|
+
}
|
|
4455
4538
|
if (ok) await flushQueued(sent);
|
|
4456
4539
|
}
|
|
4457
4540
|
async function flushQueued(sent) {
|
|
@@ -5550,8 +5633,9 @@
|
|
|
5550
5633
|
document.getElementById("model-fast").checked = Boolean(modelCfg.fast);
|
|
5551
5634
|
const spec = activeReasoningSpec();
|
|
5552
5635
|
const choices = effortChoices(spec);
|
|
5636
|
+
const currentRef = activeModelRef();
|
|
5553
5637
|
const reasonVal = clampEffort(
|
|
5554
|
-
|
|
5638
|
+
currentRef && currentRef.reasoning,
|
|
5555
5639
|
spec,
|
|
5556
5640
|
Boolean(modelCfg.fast),
|
|
5557
5641
|
);
|
|
@@ -5621,11 +5705,24 @@
|
|
|
5621
5705
|
async function applyChatModel(ref) {
|
|
5622
5706
|
if (state.kind !== "dm") return;
|
|
5623
5707
|
const bot = botById(state.id);
|
|
5624
|
-
if (!bot) return;
|
|
5708
|
+
if (!bot || !ref || !ref.provider || !ref.model) return;
|
|
5709
|
+
const spec = (() => {
|
|
5710
|
+
const hit = allModels().find(
|
|
5711
|
+
(row) => row.provider === ref.provider && row.model === ref.model,
|
|
5712
|
+
);
|
|
5713
|
+
return (hit && hit.reasoning) || null;
|
|
5714
|
+
})();
|
|
5715
|
+
const reason = clampEffort(
|
|
5716
|
+
document.getElementById("model-reasoning").value || ref.reasoning,
|
|
5717
|
+
spec,
|
|
5718
|
+
Boolean(document.getElementById("model-fast").checked),
|
|
5719
|
+
);
|
|
5720
|
+
const next = { provider: ref.provider, model: ref.model };
|
|
5721
|
+
if (reason) next.reasoning = reason;
|
|
5625
5722
|
const res = await fetch("/bots/" + encodeURIComponent(bot.id), {
|
|
5626
5723
|
method: "PATCH",
|
|
5627
5724
|
headers: { "content-type": "application/json" },
|
|
5628
|
-
body: JSON.stringify({ model:
|
|
5725
|
+
body: JSON.stringify({ model: next }),
|
|
5629
5726
|
});
|
|
5630
5727
|
if (!res.ok) {
|
|
5631
5728
|
const body = await res.json().catch(() => ({}));
|
|
@@ -5635,16 +5732,16 @@
|
|
|
5635
5732
|
const updated = await res.json();
|
|
5636
5733
|
const i = state.bots.findIndex((item) => item.id === bot.id);
|
|
5637
5734
|
if (i >= 0) state.bots[i] = { ...state.bots[i], model: updated.model };
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
})
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
}
|
|
5735
|
+
const fast = document.getElementById("model-fast").checked;
|
|
5736
|
+
if (Boolean(modelCfg.fast) !== Boolean(fast)) {
|
|
5737
|
+
await fetch("/settings/models", {
|
|
5738
|
+
method: "PUT",
|
|
5739
|
+
headers: { "content-type": "application/json" },
|
|
5740
|
+
body: JSON.stringify({ fast: fast }),
|
|
5741
|
+
}).then(async (r) => {
|
|
5742
|
+
if (r.ok) modelCfg = await r.json();
|
|
5743
|
+
});
|
|
5744
|
+
}
|
|
5648
5745
|
renderModelList();
|
|
5649
5746
|
renderNav();
|
|
5650
5747
|
}
|
package/src/public/i18n.js
CHANGED
|
@@ -90,6 +90,9 @@ var I18N_ROWS = [
|
|
|
90
90
|
["stop", "停止", "Stop"],
|
|
91
91
|
["steer.hint", "Enter 排隊 · {mod}↩ 插入這輪", "Enter to queue · {mod}↩ to steer this turn"],
|
|
92
92
|
["live.stop", "停止", "Stop"],
|
|
93
|
+
["live.pause", "暫停", "Pause"],
|
|
94
|
+
["live.continue", "繼續", "Continue"],
|
|
95
|
+
["live.paused", "已暫停", "Paused"],
|
|
93
96
|
["live.steer", "插入這輪", "Insert into turn"],
|
|
94
97
|
["steer.queue", "排隊", "Queue"],
|
|
95
98
|
["steer.insert", "插入引導", "Steer"],
|
package/src/public/md.js
CHANGED
|
@@ -327,6 +327,23 @@ function renderMarkdown(raw) {
|
|
|
327
327
|
return html.join("");
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
+
function hydrateHtmlPreviews(root) {
|
|
331
|
+
if (!root || typeof root.querySelectorAll !== "function") return;
|
|
332
|
+
root.querySelectorAll(".md-html-preview").forEach((box) => {
|
|
333
|
+
const src = box.querySelector(".md-html-src");
|
|
334
|
+
const frame = box.querySelector(".md-html-frame");
|
|
335
|
+
if (!src || !frame || frame.dataset.ready) return;
|
|
336
|
+
frame.dataset.ready = "1";
|
|
337
|
+
const raw = src.value;
|
|
338
|
+
const lang = (box.querySelector(".md-fence-lang") || {}).textContent || "";
|
|
339
|
+
frame.srcdoc = /svg/i.test(lang)
|
|
340
|
+
? '<!doctype html><html><body style="margin:0;background:#fff">' +
|
|
341
|
+
raw +
|
|
342
|
+
"</body></html>"
|
|
343
|
+
: raw;
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
330
347
|
if (typeof module !== "undefined" && module.exports) {
|
|
331
|
-
module.exports = { renderMarkdown, inlineMd };
|
|
348
|
+
module.exports = { renderMarkdown, inlineMd, hydrateHtmlPreviews };
|
|
332
349
|
}
|
package/src/public/mobile.css
CHANGED
|
@@ -57,6 +57,8 @@ textarea:focus-visible {
|
|
|
57
57
|
.navlist a,
|
|
58
58
|
.back,
|
|
59
59
|
.turn-stop,
|
|
60
|
+
.turn-pause,
|
|
61
|
+
.turn-continue,
|
|
60
62
|
.turn-steer,
|
|
61
63
|
.steer,
|
|
62
64
|
.send,
|
|
@@ -72,6 +74,8 @@ textarea:focus-visible {
|
|
|
72
74
|
.navlist a:active,
|
|
73
75
|
.back:active,
|
|
74
76
|
.turn-stop:active:not(:disabled),
|
|
77
|
+
.turn-pause:active:not(:disabled),
|
|
78
|
+
.turn-continue:active:not(:disabled),
|
|
75
79
|
.turn-steer:active:not(:disabled),
|
|
76
80
|
.steer:active:not(:disabled),
|
|
77
81
|
.send:active:not(:disabled),
|
|
@@ -83,6 +87,8 @@ textarea:focus-visible {
|
|
|
83
87
|
.navlist a,
|
|
84
88
|
.back,
|
|
85
89
|
.turn-stop,
|
|
90
|
+
.turn-pause,
|
|
91
|
+
.turn-continue,
|
|
86
92
|
.turn-steer,
|
|
87
93
|
.steer,
|
|
88
94
|
.send,
|
|
@@ -97,6 +103,8 @@ textarea:focus-visible {
|
|
|
97
103
|
.navlist a:active,
|
|
98
104
|
.back:active,
|
|
99
105
|
.turn-stop:active,
|
|
106
|
+
.turn-pause:active,
|
|
107
|
+
.turn-continue:active,
|
|
100
108
|
.turn-steer:active,
|
|
101
109
|
.steer:active,
|
|
102
110
|
.send:active,
|
|
@@ -326,6 +334,7 @@ textarea:focus-visible {
|
|
|
326
334
|
background: var(--fill);
|
|
327
335
|
overflow: auto;
|
|
328
336
|
}
|
|
337
|
+
.assistant-text .md-html-preview { overflow: hidden; }
|
|
329
338
|
.assistant-text .md-fence-bar {
|
|
330
339
|
display: flex;
|
|
331
340
|
align-items: center;
|
|
@@ -336,11 +345,101 @@ textarea:focus-visible {
|
|
|
336
345
|
color: var(--muted);
|
|
337
346
|
font-size: 0.75rem;
|
|
338
347
|
}
|
|
348
|
+
.assistant-text .md-fence-tabs {
|
|
349
|
+
display: inline-flex;
|
|
350
|
+
align-items: center;
|
|
351
|
+
gap: 2px;
|
|
352
|
+
margin-left: auto;
|
|
353
|
+
margin-right: 4px;
|
|
354
|
+
}
|
|
355
|
+
.assistant-text .md-fence-tab {
|
|
356
|
+
margin: 0;
|
|
357
|
+
min-height: var(--tap);
|
|
358
|
+
padding: 0 10px;
|
|
359
|
+
border: 0;
|
|
360
|
+
border-radius: 8px;
|
|
361
|
+
background: transparent;
|
|
362
|
+
color: var(--muted);
|
|
363
|
+
font: inherit;
|
|
364
|
+
font-size: 0.78rem;
|
|
365
|
+
cursor: pointer;
|
|
366
|
+
}
|
|
367
|
+
.assistant-text .md-fence-tab.on,
|
|
368
|
+
.assistant-text .md-fence-tab:hover { background: var(--lift); color: var(--text); }
|
|
369
|
+
.assistant-text .md-fence-acts {
|
|
370
|
+
display: flex;
|
|
371
|
+
align-items: center;
|
|
372
|
+
gap: 2px;
|
|
373
|
+
}
|
|
374
|
+
.assistant-text .md-fence-btn {
|
|
375
|
+
margin: 0;
|
|
376
|
+
width: var(--tap);
|
|
377
|
+
height: var(--tap);
|
|
378
|
+
padding: 0;
|
|
379
|
+
border: 0;
|
|
380
|
+
border-radius: 8px;
|
|
381
|
+
background: transparent;
|
|
382
|
+
color: var(--muted);
|
|
383
|
+
cursor: pointer;
|
|
384
|
+
display: inline-flex;
|
|
385
|
+
align-items: center;
|
|
386
|
+
justify-content: center;
|
|
387
|
+
}
|
|
388
|
+
.assistant-text .md-html-frame {
|
|
389
|
+
display: block;
|
|
390
|
+
width: 100%;
|
|
391
|
+
min-height: 220px;
|
|
392
|
+
height: 52vh;
|
|
393
|
+
border: 0;
|
|
394
|
+
background: #fff;
|
|
395
|
+
border-radius: 0 0 12px 12px;
|
|
396
|
+
}
|
|
397
|
+
.assistant-text .md-html-preview[data-view="code"] .md-html-frame { display: none; }
|
|
398
|
+
.assistant-text .md-html-preview[data-view="preview"] .md-pre { display: none; }
|
|
339
399
|
.assistant-text .md-pre {
|
|
340
400
|
margin: 0;
|
|
341
401
|
padding: 0.6rem 0.8rem 0.8rem;
|
|
342
402
|
overflow: auto;
|
|
343
403
|
font-size: 0.82rem;
|
|
404
|
+
white-space: pre;
|
|
405
|
+
overflow-wrap: normal;
|
|
406
|
+
word-break: normal;
|
|
407
|
+
}
|
|
408
|
+
.html-zoom {
|
|
409
|
+
position: fixed;
|
|
410
|
+
inset: 0;
|
|
411
|
+
z-index: 80;
|
|
412
|
+
display: grid;
|
|
413
|
+
grid-template-rows: auto 1fr;
|
|
414
|
+
padding: max(12px, env(safe-area-inset-top)) 12px max(12px, env(safe-area-inset-bottom));
|
|
415
|
+
background: rgba(0, 0, 0, 0.72);
|
|
416
|
+
}
|
|
417
|
+
.html-zoom[hidden] { display: none !important; }
|
|
418
|
+
.html-zoom-bar {
|
|
419
|
+
display: flex;
|
|
420
|
+
align-items: center;
|
|
421
|
+
justify-content: space-between;
|
|
422
|
+
gap: 12px;
|
|
423
|
+
margin: 0 0 12px;
|
|
424
|
+
color: var(--text);
|
|
425
|
+
}
|
|
426
|
+
.html-zoom-close {
|
|
427
|
+
margin: 0;
|
|
428
|
+
width: var(--tap);
|
|
429
|
+
height: var(--tap);
|
|
430
|
+
border: 0;
|
|
431
|
+
border-radius: 50%;
|
|
432
|
+
background: var(--fill);
|
|
433
|
+
color: var(--text);
|
|
434
|
+
font-size: 1.35rem;
|
|
435
|
+
cursor: pointer;
|
|
436
|
+
}
|
|
437
|
+
.html-zoom-frame {
|
|
438
|
+
width: 100%;
|
|
439
|
+
height: 100%;
|
|
440
|
+
border: 0;
|
|
441
|
+
border-radius: 16px;
|
|
442
|
+
background: #fff;
|
|
344
443
|
}
|
|
345
444
|
.assistant-text img.md-img {
|
|
346
445
|
max-width: 100%;
|
|
@@ -416,6 +515,8 @@ textarea:focus-visible {
|
|
|
416
515
|
margin-top: 4px;
|
|
417
516
|
}
|
|
418
517
|
.turn-stop,
|
|
518
|
+
.turn-pause,
|
|
519
|
+
.turn-continue,
|
|
419
520
|
.turn-steer,
|
|
420
521
|
.steer,
|
|
421
522
|
.send,
|
|
@@ -447,13 +548,16 @@ textarea:focus-visible {
|
|
|
447
548
|
background: var(--lift);
|
|
448
549
|
border-color: color-mix(in srgb, var(--danger) 45%, var(--line));
|
|
449
550
|
}
|
|
450
|
-
|
|
551
|
+
.turn-pause,
|
|
552
|
+
.turn-continue,
|
|
451
553
|
.turn-steer,
|
|
452
554
|
.steer {
|
|
453
555
|
color: var(--steel);
|
|
454
556
|
background: var(--fill);
|
|
455
557
|
border-color: color-mix(in srgb, var(--steel) 38%, var(--line));
|
|
456
558
|
}
|
|
559
|
+
.turn-pause:hover,
|
|
560
|
+
.turn-continue:hover,
|
|
457
561
|
.turn-steer:hover,
|
|
458
562
|
.steer:hover {
|
|
459
563
|
background: var(--lift);
|
package/src/public/mobile.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<link rel="icon" href="/favicon-32.png" type="image/png" sizes="32x32" />
|
|
10
10
|
<link rel="icon" href="/favicon-16.png" type="image/png" sizes="16x16" />
|
|
11
11
|
<title>Guild — 外出</title>
|
|
12
|
-
<link rel="stylesheet" href="/mobile.css?v=
|
|
12
|
+
<link rel="stylesheet" href="/mobile.css?v=html-preview" />
|
|
13
13
|
<script src="/i18n.js"></script>
|
|
14
14
|
<script src="/md.js"></script>
|
|
15
15
|
</head>
|
|
@@ -49,6 +49,13 @@
|
|
|
49
49
|
</form>
|
|
50
50
|
</section>
|
|
51
51
|
<div class="flash" id="flash" role="status" aria-live="polite" hidden></div>
|
|
52
|
+
<div class="html-zoom" id="html-zoom" hidden>
|
|
53
|
+
<div class="html-zoom-bar">
|
|
54
|
+
<strong data-i18n="html.zoom">HTML 預覽</strong>
|
|
55
|
+
<button type="button" class="html-zoom-close" id="html-zoom-close" title="關閉" aria-label="關閉" data-i18n-title="close" data-i18n-aria="close">×</button>
|
|
56
|
+
</div>
|
|
57
|
+
<iframe class="html-zoom-frame" id="html-zoom-frame" sandbox="allow-scripts" title="HTML preview large"></iframe>
|
|
58
|
+
</div>
|
|
52
59
|
<script>
|
|
53
60
|
(function () {
|
|
54
61
|
const COLORS = ["#7c6af7", "#5b8def", "#2ea887", "#e07a3d", "#d4537e"];
|
|
@@ -141,6 +148,20 @@
|
|
|
141
148
|
? "/dms/" + encodeURIComponent(r.id) + "/abort"
|
|
142
149
|
: "/channels/" + encodeURIComponent(r.id) + "/abort";
|
|
143
150
|
}
|
|
151
|
+
function pauseUrl() {
|
|
152
|
+
const r = currentRoomRef();
|
|
153
|
+
if (!r) return "";
|
|
154
|
+
return r.kind === "dm"
|
|
155
|
+
? "/dms/" + encodeURIComponent(r.id) + "/pause"
|
|
156
|
+
: "/channels/" + encodeURIComponent(r.id) + "/pause";
|
|
157
|
+
}
|
|
158
|
+
function continueUrl() {
|
|
159
|
+
const r = currentRoomRef();
|
|
160
|
+
if (!r) return "";
|
|
161
|
+
return r.kind === "dm"
|
|
162
|
+
? "/dms/" + encodeURIComponent(r.id) + "/continue"
|
|
163
|
+
: "/channels/" + encodeURIComponent(r.id) + "/continue";
|
|
164
|
+
}
|
|
144
165
|
function steerUrl() {
|
|
145
166
|
const r = currentRoomRef();
|
|
146
167
|
if (!r) return "";
|
|
@@ -225,6 +246,7 @@
|
|
|
225
246
|
thinking: row.thinking || "",
|
|
226
247
|
steps: Array.isArray(row.steps) ? row.steps.slice(-5) : [],
|
|
227
248
|
startedAt: row.startedAt || "",
|
|
249
|
+
paused: Boolean(row.paused),
|
|
228
250
|
}));
|
|
229
251
|
}
|
|
230
252
|
function isBusy() {
|
|
@@ -342,10 +364,11 @@
|
|
|
342
364
|
}
|
|
343
365
|
function formatBody(text) {
|
|
344
366
|
const cleaned = visibleAssistantText(text);
|
|
345
|
-
|
|
367
|
+
const clipped =
|
|
368
|
+
cleaned.length > 48000 ? cleaned.slice(0, 48000) + "\n…" : cleaned;
|
|
346
369
|
return typeof renderMarkdown === "function"
|
|
347
|
-
? renderMarkdown(
|
|
348
|
-
: escapeHtml(
|
|
370
|
+
? renderMarkdown(clipped)
|
|
371
|
+
: escapeHtml(clipped);
|
|
349
372
|
}
|
|
350
373
|
function liveStepLabel(name) {
|
|
351
374
|
if (name === "think") return t("think");
|
|
@@ -409,19 +432,35 @@
|
|
|
409
432
|
'</span><span class="handle">@' +
|
|
410
433
|
escapeHtml(bot.handle) +
|
|
411
434
|
'</span></div><div class="turn-live"><div class="turn-status">' +
|
|
412
|
-
t("deepDiving") +
|
|
435
|
+
(live.paused ? t("live.paused") : t("deepDiving")) +
|
|
413
436
|
clock +
|
|
414
437
|
"</div>" +
|
|
415
438
|
liveStepsHtml(live.steps) +
|
|
416
|
-
'<div class="turn-actions"
|
|
439
|
+
'<div class="turn-actions">' +
|
|
440
|
+
(live.paused
|
|
441
|
+
? '<button type="button" class="turn-continue" data-live-continue="' +
|
|
442
|
+
escapeHtml(bot.id) +
|
|
443
|
+
'">' +
|
|
444
|
+
t("live.continue") +
|
|
445
|
+
"</button>"
|
|
446
|
+
: '<button type="button" class="turn-pause" data-live-pause="' +
|
|
447
|
+
escapeHtml(bot.id) +
|
|
448
|
+
'">' +
|
|
449
|
+
t("live.pause") +
|
|
450
|
+
"</button>") +
|
|
451
|
+
'<button type="button" class="turn-stop" data-live-stop="' +
|
|
417
452
|
escapeHtml(bot.id) +
|
|
418
453
|
'">' +
|
|
419
454
|
t("live.stop") +
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
455
|
+
"</button>" +
|
|
456
|
+
(live.paused
|
|
457
|
+
? ""
|
|
458
|
+
: '<button type="button" class="turn-steer" data-live-steer="' +
|
|
459
|
+
escapeHtml(bot.id) +
|
|
460
|
+
'">' +
|
|
461
|
+
t("live.steer") +
|
|
462
|
+
"</button>") +
|
|
463
|
+
"</div></div></div></article>"
|
|
425
464
|
);
|
|
426
465
|
})
|
|
427
466
|
.join("");
|
|
@@ -482,8 +521,24 @@
|
|
|
482
521
|
"</div>"
|
|
483
522
|
: "";
|
|
484
523
|
root.innerHTML = notice + rows.map(msgHtml).join("") + turnStatusHtml();
|
|
524
|
+
if (typeof hydrateHtmlPreviews === "function") {
|
|
525
|
+
hydrateHtmlPreviews(root);
|
|
526
|
+
}
|
|
485
527
|
if (pin) root.scrollTop = root.scrollHeight;
|
|
486
528
|
}
|
|
529
|
+
function openHtmlZoom(raw) {
|
|
530
|
+
const wrap = document.getElementById("html-zoom");
|
|
531
|
+
const frame = document.getElementById("html-zoom-frame");
|
|
532
|
+
if (!wrap || !frame) return;
|
|
533
|
+
frame.srcdoc = raw || "";
|
|
534
|
+
wrap.hidden = false;
|
|
535
|
+
}
|
|
536
|
+
function closeHtmlZoom() {
|
|
537
|
+
const wrap = document.getElementById("html-zoom");
|
|
538
|
+
const frame = document.getElementById("html-zoom-frame");
|
|
539
|
+
if (frame) frame.srcdoc = "";
|
|
540
|
+
if (wrap) wrap.hidden = true;
|
|
541
|
+
}
|
|
487
542
|
function steerModLabel() {
|
|
488
543
|
return /Mac|iPhone|iPad/.test(navigator.platform || "") ? "\u2318" : "Ctrl+";
|
|
489
544
|
}
|
|
@@ -771,6 +826,48 @@
|
|
|
771
826
|
renderThread();
|
|
772
827
|
await loadMessages().catch(() => {});
|
|
773
828
|
}
|
|
829
|
+
async function pauseTurn(botId) {
|
|
830
|
+
try {
|
|
831
|
+
await fetch(pauseUrl(), {
|
|
832
|
+
method: "POST",
|
|
833
|
+
headers: { "content-type": "application/json" },
|
|
834
|
+
body: JSON.stringify(botId ? { botId: botId } : {}),
|
|
835
|
+
});
|
|
836
|
+
} catch {
|
|
837
|
+
/* ignore */
|
|
838
|
+
}
|
|
839
|
+
(state.lives || []).forEach((row) => {
|
|
840
|
+
if (!botId || row.botId === botId) row.paused = true;
|
|
841
|
+
});
|
|
842
|
+
renderThread();
|
|
843
|
+
await pollLive().catch(() => {});
|
|
844
|
+
}
|
|
845
|
+
async function continueTurn(botId) {
|
|
846
|
+
(state.lives || []).forEach((row) => {
|
|
847
|
+
if (row.botId === botId) row.paused = false;
|
|
848
|
+
});
|
|
849
|
+
state.posting = true;
|
|
850
|
+
syncComposer();
|
|
851
|
+
renderThread();
|
|
852
|
+
try {
|
|
853
|
+
const res = await fetch(continueUrl(), {
|
|
854
|
+
method: "POST",
|
|
855
|
+
headers: { "content-type": "application/json" },
|
|
856
|
+
body: JSON.stringify({ botId: botId }),
|
|
857
|
+
});
|
|
858
|
+
const body = await res.json().catch(() => ({}));
|
|
859
|
+
if (!res.ok) throw new Error(body.error || "continue failed");
|
|
860
|
+
await loadMessages();
|
|
861
|
+
await pollLive();
|
|
862
|
+
} catch (err) {
|
|
863
|
+
toast(err.message || String(err));
|
|
864
|
+
await pollLive().catch(() => {});
|
|
865
|
+
} finally {
|
|
866
|
+
state.posting = false;
|
|
867
|
+
syncComposer();
|
|
868
|
+
schedule();
|
|
869
|
+
}
|
|
870
|
+
}
|
|
774
871
|
async function sendSteer(raw, botId) {
|
|
775
872
|
const text = String(raw || "").trim();
|
|
776
873
|
if (!text || !state.kind) return;
|
|
@@ -886,13 +983,68 @@
|
|
|
886
983
|
document.getElementById("steer").addEventListener("click", () => {
|
|
887
984
|
steerDraft().catch((err) => toast(err.message || String(err)));
|
|
888
985
|
});
|
|
986
|
+
document.getElementById("html-zoom-close").addEventListener("click", () => {
|
|
987
|
+
closeHtmlZoom();
|
|
988
|
+
});
|
|
989
|
+
document.getElementById("html-zoom").addEventListener("click", (event) => {
|
|
990
|
+
if (event.target.id === "html-zoom") closeHtmlZoom();
|
|
991
|
+
});
|
|
889
992
|
document.getElementById("thread").addEventListener("click", (event) => {
|
|
993
|
+
const htmlExpand = event.target.closest("[data-html-expand]");
|
|
994
|
+
if (htmlExpand) {
|
|
995
|
+
event.preventDefault();
|
|
996
|
+
const box = htmlExpand.closest(".md-html-preview");
|
|
997
|
+
const src = box && box.querySelector(".md-html-src");
|
|
998
|
+
if (src) openHtmlZoom(src.value);
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
const htmlView = event.target.closest("[data-html-view]");
|
|
1002
|
+
if (htmlView) {
|
|
1003
|
+
event.preventDefault();
|
|
1004
|
+
const box = htmlView.closest(".md-html-preview");
|
|
1005
|
+
if (!box) return;
|
|
1006
|
+
const view = htmlView.getAttribute("data-html-view");
|
|
1007
|
+
box.setAttribute("data-view", view);
|
|
1008
|
+
const frame = box.querySelector(".md-html-frame");
|
|
1009
|
+
const pre = box.querySelector(".md-pre");
|
|
1010
|
+
if (frame) frame.hidden = view !== "preview";
|
|
1011
|
+
if (pre) pre.hidden = view !== "code";
|
|
1012
|
+
box.querySelectorAll("[data-html-view]").forEach((tab) => {
|
|
1013
|
+
tab.classList.toggle("on", tab.getAttribute("data-html-view") === view);
|
|
1014
|
+
});
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
const fenceCopy = event.target.closest("[data-fence-copy]");
|
|
1018
|
+
if (fenceCopy) {
|
|
1019
|
+
event.preventDefault();
|
|
1020
|
+
const box = fenceCopy.closest(".md-fence");
|
|
1021
|
+
const src = box && (box.querySelector(".md-html-src") || box.querySelector("code"));
|
|
1022
|
+
const text = src && "value" in src ? src.value : src ? src.textContent : "";
|
|
1023
|
+
if (text && navigator.clipboard) {
|
|
1024
|
+
navigator.clipboard.writeText(text).catch(() => {});
|
|
1025
|
+
}
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
890
1028
|
const stop = event.target.closest("[data-live-stop]");
|
|
891
1029
|
if (stop) {
|
|
892
1030
|
event.preventDefault();
|
|
893
1031
|
stopTurn(stop.getAttribute("data-live-stop"));
|
|
894
1032
|
return;
|
|
895
1033
|
}
|
|
1034
|
+
const pause = event.target.closest("[data-live-pause]");
|
|
1035
|
+
if (pause) {
|
|
1036
|
+
event.preventDefault();
|
|
1037
|
+
pauseTurn(pause.getAttribute("data-live-pause"));
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
const cont = event.target.closest("[data-live-continue]");
|
|
1041
|
+
if (cont) {
|
|
1042
|
+
event.preventDefault();
|
|
1043
|
+
continueTurn(cont.getAttribute("data-live-continue")).catch((err) =>
|
|
1044
|
+
toast(err.message || String(err)),
|
|
1045
|
+
);
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
896
1048
|
const steer = event.target.closest("[data-live-steer]");
|
|
897
1049
|
if (!steer) return;
|
|
898
1050
|
event.preventDefault();
|
package/src/router.ts
CHANGED
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
getBotDetail,
|
|
25
25
|
getLiveTurn,
|
|
26
26
|
abortLiveTurn,
|
|
27
|
+
pauseLiveTurn,
|
|
28
|
+
continueLiveTurn,
|
|
27
29
|
healthPayload,
|
|
28
30
|
importSkills,
|
|
29
31
|
mergeModelsFile,
|
|
@@ -271,7 +273,8 @@ function modelRefFrom(value: unknown): ModelRef | null {
|
|
|
271
273
|
const provider = str(rec, "provider").trim();
|
|
272
274
|
const model = str(rec, "model").trim();
|
|
273
275
|
if (!provider || !model) return null;
|
|
274
|
-
|
|
276
|
+
const reasoning = str(rec, "reasoning").trim();
|
|
277
|
+
return reasoning ? { provider, model, reasoning } : { provider, model };
|
|
275
278
|
}
|
|
276
279
|
|
|
277
280
|
function strList(record: Record<string, unknown>, key: string): string[] {
|
|
@@ -709,6 +712,66 @@ export async function handleRequest(
|
|
|
709
712
|
return;
|
|
710
713
|
}
|
|
711
714
|
|
|
715
|
+
const channelPause = path.match(/^\/channels\/([^/]+)\/pause$/);
|
|
716
|
+
if (channelPause && method === "POST") {
|
|
717
|
+
const body = asRecord(await readJson(req));
|
|
718
|
+
json(
|
|
719
|
+
res,
|
|
720
|
+
200,
|
|
721
|
+
pauseLiveTurn(
|
|
722
|
+
store,
|
|
723
|
+
decodeURIComponent(channelPause[1]),
|
|
724
|
+
str(body, "botId") || undefined,
|
|
725
|
+
),
|
|
726
|
+
);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
const dmPause = path.match(/^\/dms\/([^/]+)\/pause$/);
|
|
730
|
+
if (dmPause && method === "POST") {
|
|
731
|
+
const room = openDm(store, decodeURIComponent(dmPause[1]));
|
|
732
|
+
const body = asRecord(await readJson(req));
|
|
733
|
+
json(
|
|
734
|
+
res,
|
|
735
|
+
200,
|
|
736
|
+
pauseLiveTurn(store, room.id, str(body, "botId") || undefined),
|
|
737
|
+
);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const channelContinue = path.match(/^\/channels\/([^/]+)\/continue$/);
|
|
742
|
+
if (channelContinue && method === "POST") {
|
|
743
|
+
const body = asRecord(await readJson(req));
|
|
744
|
+
json(
|
|
745
|
+
res,
|
|
746
|
+
200,
|
|
747
|
+
await continueLiveTurn(
|
|
748
|
+
store,
|
|
749
|
+
decodeURIComponent(channelContinue[1]),
|
|
750
|
+
str(body, "botId"),
|
|
751
|
+
env,
|
|
752
|
+
extras,
|
|
753
|
+
),
|
|
754
|
+
);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const dmContinue = path.match(/^\/dms\/([^/]+)\/continue$/);
|
|
758
|
+
if (dmContinue && method === "POST") {
|
|
759
|
+
const room = openDm(store, decodeURIComponent(dmContinue[1]));
|
|
760
|
+
const body = asRecord(await readJson(req));
|
|
761
|
+
json(
|
|
762
|
+
res,
|
|
763
|
+
200,
|
|
764
|
+
await continueLiveTurn(
|
|
765
|
+
store,
|
|
766
|
+
room.id,
|
|
767
|
+
str(body, "botId"),
|
|
768
|
+
env,
|
|
769
|
+
extras,
|
|
770
|
+
),
|
|
771
|
+
);
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
|
|
712
775
|
const channelSteer = path.match(/^\/channels\/([^/]+)\/steer$/);
|
|
713
776
|
if (channelSteer && method === "POST") {
|
|
714
777
|
const body = asRecord(await readJson(req));
|
package/src/store.ts
CHANGED
|
@@ -101,6 +101,9 @@ export type LiveTurn = {
|
|
|
101
101
|
startedAt?: string;
|
|
102
102
|
/** Full-ish tool history for Trajectory. Stripped from GET /live. */
|
|
103
103
|
traces?: LiveTrace[];
|
|
104
|
+
/** Seat assignment text, kept so Continue can resume after Pause. */
|
|
105
|
+
asked?: string;
|
|
106
|
+
paused?: boolean;
|
|
104
107
|
};
|
|
105
108
|
|
|
106
109
|
export class GuildStore {
|
|
@@ -366,7 +369,6 @@ export class GuildStore {
|
|
|
366
369
|
beginTurn(roomId: string, botIds: string[] = [""]): AbortSignal {
|
|
367
370
|
const controller = new AbortController();
|
|
368
371
|
const ids = botIds.length ? botIds : [""];
|
|
369
|
-
for (const botId of ids) this.bindBotAbort(roomId, botId, controller);
|
|
370
372
|
this.turnGroups.set(controller.signal, {
|
|
371
373
|
roomId,
|
|
372
374
|
botIds: new Set(ids),
|
|
@@ -375,43 +377,54 @@ export class GuildStore {
|
|
|
375
377
|
return controller.signal;
|
|
376
378
|
}
|
|
377
379
|
|
|
380
|
+
/**
|
|
381
|
+
* Per-seat AbortController, child of the turn group. Pause/Stop one bot
|
|
382
|
+
* without taking the rest of the wave down.
|
|
383
|
+
*/
|
|
384
|
+
armBotTurn(roomId: string, botId: string, parent: AbortSignal): AbortSignal {
|
|
385
|
+
const controller = new AbortController();
|
|
386
|
+
const onParent = () => {
|
|
387
|
+
if (!controller.signal.aborted) controller.abort();
|
|
388
|
+
};
|
|
389
|
+
if (parent.aborted) onParent();
|
|
390
|
+
else parent.addEventListener("abort", onParent, { once: true });
|
|
391
|
+
this.bindBotAbort(roomId, botId, controller);
|
|
392
|
+
const group = this.turnGroups.get(parent);
|
|
393
|
+
if (group && group.roomId === roomId) group.botIds.add(botId);
|
|
394
|
+
return controller.signal;
|
|
395
|
+
}
|
|
396
|
+
|
|
378
397
|
adoptTurn(roomId: string, botId: string, signal: AbortSignal): void {
|
|
379
398
|
const group = this.turnGroups.get(signal);
|
|
380
399
|
if (!group || group.roomId !== roomId) return;
|
|
381
|
-
this.bindBotAbort(roomId, botId, group.controller);
|
|
382
400
|
group.botIds.add(botId);
|
|
383
401
|
}
|
|
384
402
|
|
|
403
|
+
private dropBotLive(roomId: string, botId: string): boolean {
|
|
404
|
+
const live = this.liveTurns.get(roomId);
|
|
405
|
+
const steers = this.pendingSteers.get(roomId);
|
|
406
|
+
const room = this.botAborts.get(roomId);
|
|
407
|
+
const hadLive = Boolean(live?.delete(botId));
|
|
408
|
+
steers?.delete(botId);
|
|
409
|
+
room?.delete(botId);
|
|
410
|
+
if (live && live.size === 0) this.liveTurns.delete(roomId);
|
|
411
|
+
if (steers && steers.size === 0) this.pendingSteers.delete(roomId);
|
|
412
|
+
if (room && room.size === 0) this.botAborts.delete(roomId);
|
|
413
|
+
for (const [sig, group] of this.turnGroups) {
|
|
414
|
+
if (group.roomId !== roomId || !group.botIds.has(botId)) continue;
|
|
415
|
+
group.botIds.delete(botId);
|
|
416
|
+
if (group.botIds.size === 0) this.turnGroups.delete(sig);
|
|
417
|
+
}
|
|
418
|
+
return hadLive;
|
|
419
|
+
}
|
|
420
|
+
|
|
385
421
|
abortTurn(roomId: string, botId?: string): boolean {
|
|
386
422
|
if (botId) {
|
|
387
|
-
const
|
|
388
|
-
const
|
|
389
|
-
if (!controller)
|
|
390
|
-
const live = this.liveTurns.get(roomId);
|
|
391
|
-
const steers = this.pendingSteers.get(roomId);
|
|
392
|
-
const hadLive = Boolean(live?.delete(botId));
|
|
393
|
-
steers?.delete(botId);
|
|
394
|
-
if (live && live.size === 0) this.liveTurns.delete(roomId);
|
|
395
|
-
if (steers && steers.size === 0) this.pendingSteers.delete(roomId);
|
|
396
|
-
this.spillTrajectoryIfIdle(roomId);
|
|
397
|
-
return hadLive;
|
|
398
|
-
}
|
|
399
|
-
const group = this.turnGroups.get(controller.signal);
|
|
400
|
-
const ids = group ? [...group.botIds] : [botId];
|
|
401
|
-
this.turnGroups.delete(controller.signal);
|
|
402
|
-
const live = this.liveTurns.get(roomId);
|
|
403
|
-
const steers = this.pendingSteers.get(roomId);
|
|
404
|
-
for (const id of ids) {
|
|
405
|
-
room?.delete(id);
|
|
406
|
-
live?.delete(id);
|
|
407
|
-
steers?.delete(id);
|
|
408
|
-
}
|
|
409
|
-
if (room && room.size === 0) this.botAborts.delete(roomId);
|
|
410
|
-
if (live && live.size === 0) this.liveTurns.delete(roomId);
|
|
411
|
-
if (steers && steers.size === 0) this.pendingSteers.delete(roomId);
|
|
412
|
-
if (!controller.signal.aborted) controller.abort();
|
|
423
|
+
const controller = this.botAborts.get(roomId)?.get(botId);
|
|
424
|
+
const hadLive = this.dropBotLive(roomId, botId);
|
|
425
|
+
if (controller && !controller.signal.aborted) controller.abort();
|
|
413
426
|
this.spillTrajectoryIfIdle(roomId);
|
|
414
|
-
return
|
|
427
|
+
return hadLive || Boolean(controller);
|
|
415
428
|
}
|
|
416
429
|
const room = this.botAborts.get(roomId);
|
|
417
430
|
this.botAborts.delete(roomId);
|
|
@@ -419,10 +432,18 @@ export class GuildStore {
|
|
|
419
432
|
this.pendingSteers.delete(roomId);
|
|
420
433
|
let aborted = false;
|
|
421
434
|
const seen = new Set<AbortController>();
|
|
435
|
+
for (const [sig, group] of [...this.turnGroups]) {
|
|
436
|
+
if (group.roomId !== roomId) continue;
|
|
437
|
+
this.turnGroups.delete(sig);
|
|
438
|
+
if (!group.controller.signal.aborted) {
|
|
439
|
+
group.controller.abort();
|
|
440
|
+
aborted = true;
|
|
441
|
+
}
|
|
442
|
+
seen.add(group.controller);
|
|
443
|
+
}
|
|
422
444
|
for (const controller of room?.values() ?? []) {
|
|
423
445
|
if (seen.has(controller)) continue;
|
|
424
446
|
seen.add(controller);
|
|
425
|
-
this.turnGroups.delete(controller.signal);
|
|
426
447
|
if (!controller.signal.aborted) {
|
|
427
448
|
controller.abort();
|
|
428
449
|
aborted = true;
|
|
@@ -432,6 +453,37 @@ export class GuildStore {
|
|
|
432
453
|
return aborted || Boolean(room);
|
|
433
454
|
}
|
|
434
455
|
|
|
456
|
+
pauseTurn(roomId: string, botId?: string): boolean {
|
|
457
|
+
const ids = botId
|
|
458
|
+
? [botId]
|
|
459
|
+
: [...(this.liveTurns.get(roomId)?.keys() ?? [])];
|
|
460
|
+
let any = false;
|
|
461
|
+
for (const id of ids) {
|
|
462
|
+
if (!id) continue;
|
|
463
|
+
const live = this.getLiveBotTurn(roomId, id);
|
|
464
|
+
if (!live) continue;
|
|
465
|
+
const traces = (live.traces || []).map((tr) =>
|
|
466
|
+
tr.running ? { ...tr, running: false, text: tr.text || "paused" } : tr,
|
|
467
|
+
);
|
|
468
|
+
const steps = (live.steps || []).map((step) =>
|
|
469
|
+
step.running ? { ...step, running: false } : step,
|
|
470
|
+
);
|
|
471
|
+
this.setLiveTurn(roomId, {
|
|
472
|
+
...live,
|
|
473
|
+
traces,
|
|
474
|
+
steps,
|
|
475
|
+
paused: true,
|
|
476
|
+
});
|
|
477
|
+
const controller = this.botAborts.get(roomId)?.get(id);
|
|
478
|
+
this.botAborts.get(roomId)?.delete(id);
|
|
479
|
+
const room = this.botAborts.get(roomId);
|
|
480
|
+
if (room && room.size === 0) this.botAborts.delete(roomId);
|
|
481
|
+
if (controller && !controller.signal.aborted) controller.abort();
|
|
482
|
+
any = true;
|
|
483
|
+
}
|
|
484
|
+
return any;
|
|
485
|
+
}
|
|
486
|
+
|
|
435
487
|
endTurn(roomId: string, signal?: AbortSignal): void {
|
|
436
488
|
const group = signal ? this.turnGroups.get(signal) : undefined;
|
|
437
489
|
if (group) {
|
|
@@ -440,7 +492,11 @@ export class GuildStore {
|
|
|
440
492
|
const live = this.liveTurns.get(roomId);
|
|
441
493
|
const steers = this.pendingSteers.get(roomId);
|
|
442
494
|
for (const botId of group.botIds) {
|
|
443
|
-
if (
|
|
495
|
+
if (live?.get(botId)?.paused) {
|
|
496
|
+
room?.delete(botId);
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
room?.delete(botId);
|
|
444
500
|
live?.delete(botId);
|
|
445
501
|
steers?.delete(botId);
|
|
446
502
|
}
|
|
@@ -449,8 +505,14 @@ export class GuildStore {
|
|
|
449
505
|
if (steers && steers.size === 0) this.pendingSteers.delete(roomId);
|
|
450
506
|
if (!group.controller.signal.aborted) group.controller.abort();
|
|
451
507
|
} else {
|
|
508
|
+
const live = this.liveTurns.get(roomId);
|
|
509
|
+
const kept = new Map<string, LiveTurn>();
|
|
510
|
+
for (const [id, turn] of live ?? []) {
|
|
511
|
+
if (turn.paused) kept.set(id, turn);
|
|
512
|
+
}
|
|
452
513
|
this.botAborts.delete(roomId);
|
|
453
|
-
this.
|
|
514
|
+
if (kept.size) this.liveTurns.set(roomId, kept);
|
|
515
|
+
else this.clearLiveTurn(roomId);
|
|
454
516
|
}
|
|
455
517
|
this.spillTrajectoryIfIdle(roomId);
|
|
456
518
|
}
|
|
@@ -6,7 +6,12 @@ export type HealthResponse = {
|
|
|
6
6
|
|
|
7
7
|
export type BotStatus = "bench" | "staffed" | "running" | "retired";
|
|
8
8
|
|
|
9
|
-
export type ModelRef = {
|
|
9
|
+
export type ModelRef = {
|
|
10
|
+
provider: string;
|
|
11
|
+
model: string;
|
|
12
|
+
/** Seat-specific effort. Missing → that model's catalog default. */
|
|
13
|
+
reasoning?: string;
|
|
14
|
+
};
|
|
10
15
|
|
|
11
16
|
export type LibraryKind =
|
|
12
17
|
| "souls"
|
|
@@ -169,7 +174,7 @@ export type AuxRole =
|
|
|
169
174
|
|
|
170
175
|
export type ModelsFile = {
|
|
171
176
|
default?: ModelRef | null;
|
|
172
|
-
/**
|
|
177
|
+
/** Guild-default effort when a seat has no `model.reasoning`. */
|
|
173
178
|
reasoning?: string;
|
|
174
179
|
fast?: boolean;
|
|
175
180
|
aux?: Partial<Record<AuxRole, ModelRef | null>>;
|