Ship Assistent 0.15.1: keep composer writable during hung Writing streams.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+112
-17
@@ -1316,6 +1316,10 @@
|
||||
busyStarted: 0,
|
||||
gotDelta: false,
|
||||
busyTimer: null,
|
||||
lastDeltaAt: 0,
|
||||
streamStallTimer: null,
|
||||
turnSettled: false,
|
||||
lastBusyPhaseShown: "",
|
||||
slots: [],
|
||||
selectedSlotId: "ref1",
|
||||
refSeq: 1,
|
||||
@@ -1511,16 +1515,22 @@
|
||||
refining: "prep",
|
||||
compressing: "compress"
|
||||
};
|
||||
activityStep(`phase:${state.busyPhase}`, {
|
||||
kind: phaseKind[state.busyPhase] || "think",
|
||||
label: text,
|
||||
status: "running"
|
||||
});
|
||||
const a = getActivity();
|
||||
if (a && Array.isArray(a.steps)) {
|
||||
for (const s of a.steps) {
|
||||
if (s.id.startsWith("phase:") && s.id !== `phase:${state.busyPhase}` && s.status === "running") {
|
||||
a.done(s.id);
|
||||
if (state.lastBusyPhaseShown !== state.busyPhase) {
|
||||
state.lastBusyPhaseShown = state.busyPhase;
|
||||
if (state.busyPhase === "streaming" || state.busyPhase === "refining") {
|
||||
activityDone("think");
|
||||
}
|
||||
activityStep(`phase:${state.busyPhase}`, {
|
||||
kind: phaseKind[state.busyPhase] || "think",
|
||||
label: text,
|
||||
status: "running"
|
||||
});
|
||||
const a = getActivity();
|
||||
if (a && Array.isArray(a.steps)) {
|
||||
for (const s of a.steps) {
|
||||
if (s.id.startsWith("phase:") && s.id !== `phase:${state.busyPhase}` && s.status === "running") {
|
||||
a.done(s.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1544,12 +1554,15 @@
|
||||
state.busyPhase = phase || "thinking";
|
||||
$2("swarm_assistent_root")?.classList.add("sa-is-busy");
|
||||
$2("sa_composer")?.classList.add("sa-composer-busy");
|
||||
state.lastBusyPhaseShown = "";
|
||||
const send = $2("sa_btn_send");
|
||||
if (send) {
|
||||
send.disabled = true;
|
||||
send.disabled = false;
|
||||
}
|
||||
const input = $2("sa_input");
|
||||
if (input) {
|
||||
input.readOnly = false;
|
||||
input.disabled = false;
|
||||
input.classList.add("sa-input-busy");
|
||||
}
|
||||
const bar = $2("sa_livebar");
|
||||
@@ -1573,8 +1586,10 @@
|
||||
clearInterval(state.busyTimer);
|
||||
state.busyTimer = null;
|
||||
}
|
||||
clearStreamStall();
|
||||
const elapsed = Date.now() - (state.busyStarted || Date.now());
|
||||
state.busyPhase = "idle";
|
||||
state.lastBusyPhaseShown = "";
|
||||
activityFinish(finalStatus || "\u0413\u043E\u0442\u043E\u0432\u043E");
|
||||
$2("swarm_assistent_root")?.classList.remove("sa-is-busy");
|
||||
$2("sa_composer")?.classList.remove("sa-composer-busy");
|
||||
@@ -5545,6 +5560,53 @@ ${patch.prompt}`;
|
||||
state.chatEpoch = (state.chatEpoch || 0) + 1;
|
||||
return state.chatEpoch;
|
||||
}
|
||||
function clearStreamStall() {
|
||||
if (state.streamStallTimer) {
|
||||
clearInterval(state.streamStallTimer);
|
||||
state.streamStallTimer = null;
|
||||
}
|
||||
}
|
||||
function armStreamStall(chatEpoch, onStall) {
|
||||
clearStreamStall();
|
||||
state.lastDeltaAt = Date.now();
|
||||
state.streamStallTimer = setInterval(() => {
|
||||
if (chatEpoch !== state.chatEpoch) {
|
||||
clearStreamStall();
|
||||
return;
|
||||
}
|
||||
if (!state.gotDelta || !state.busy || state.generating || state.turnSettled) {
|
||||
return;
|
||||
}
|
||||
const waitMs = state.streamFenceDone ? 4e3 : 15e3;
|
||||
if (Date.now() - (state.lastDeltaAt || 0) < waitMs) {
|
||||
return;
|
||||
}
|
||||
clearStreamStall();
|
||||
const reply = state.streamText || state.streamEl?.querySelector(".sa-msg-body")?.textContent || "";
|
||||
try {
|
||||
onStall(String(reply || ""));
|
||||
} catch (e) {
|
||||
console.warn("Assistent stream stall", e);
|
||||
}
|
||||
}, 800);
|
||||
}
|
||||
function settlePartialStream() {
|
||||
const text = String(state.streamText || "").trim();
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
const persona = $2("sa_persona")?.value || localStorage.getItem(LS_PERSONA) || "neutral";
|
||||
const pack = $2("sa_pack")?.value || defaultPackId();
|
||||
const prose = typeof extractPatch2 === "function" ? extractPatch2(text).prose || text : text;
|
||||
if (state.streamEl) {
|
||||
finalizeStreamMessage(text, []);
|
||||
} else {
|
||||
appendMessage("assistant", prose);
|
||||
}
|
||||
state.history.push({ role: "assistant", content: prose, persona, pack });
|
||||
persistHistory();
|
||||
state.turnSettled = true;
|
||||
}
|
||||
function clearInFlightUi({ status } = {}) {
|
||||
state.busy = false;
|
||||
state.generating = false;
|
||||
@@ -5567,6 +5629,7 @@ ${patch.prompt}`;
|
||||
}
|
||||
}
|
||||
function abortInFlightWork({ status, interruptSwarm = false } = {}) {
|
||||
settlePartialStream();
|
||||
bumpChatEpoch();
|
||||
cancelWaitForNewImage();
|
||||
if (interruptSwarm) {
|
||||
@@ -5584,6 +5647,7 @@ ${patch.prompt}`;
|
||||
clearInFlightUi({ status: status != null ? status : "" });
|
||||
}
|
||||
function doInterruptNow() {
|
||||
settlePartialStream();
|
||||
bumpChatEpoch();
|
||||
cancelWaitForNewImage();
|
||||
try {
|
||||
@@ -6145,6 +6209,7 @@ ${patch.prompt}`;
|
||||
setAssistantBody(state.streamEl, "", { live: true });
|
||||
}
|
||||
state.gotDelta = true;
|
||||
state.lastDeltaAt = Date.now();
|
||||
state.expectColdLoad = false;
|
||||
if (state.busyPhase !== "refining") {
|
||||
setBusyPhase("streaming");
|
||||
@@ -6166,6 +6231,9 @@ ${patch.prompt}`;
|
||||
state.streamText = "";
|
||||
state.streamFenceDone = false;
|
||||
if (!el) {
|
||||
if (state.turnSettled) {
|
||||
return;
|
||||
}
|
||||
appendMessage("assistant", fullReply, null, civitaiResults, meta || void 0);
|
||||
return;
|
||||
}
|
||||
@@ -8790,9 +8858,6 @@ ${HELP_TEXT}`);
|
||||
return true;
|
||||
}
|
||||
async function sendChat(opts = {}) {
|
||||
if ((state.busy || state.generating) && !isContinuationTurn(opts)) {
|
||||
return;
|
||||
}
|
||||
if (isTrainingLocked() && !isContinuationTurn(opts)) {
|
||||
setStatus("\u0418\u0434\u0451\u0442 \u0442\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A\u0430 \u2014 \u0447\u0430\u0442 \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D");
|
||||
return;
|
||||
@@ -8802,6 +8867,13 @@ ${HELP_TEXT}`);
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
if (state.generating && !isContinuationTurn(opts)) {
|
||||
setStatus("\u0418\u0434\u0451\u0442 Generate \u2014 \u043D\u0430\u0436\u043C\u0438 \u0421\u0442\u043E\u043F, \u043F\u043E\u0442\u043E\u043C \u043E\u0442\u043F\u0440\u0430\u0432\u044C");
|
||||
return;
|
||||
}
|
||||
if (state.busy && !isContinuationTurn(opts)) {
|
||||
abortInFlightWork({ status: "\u041D\u043E\u0432\u043E\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435", interruptSwarm: false });
|
||||
}
|
||||
if (!isMachineTurn(opts)) {
|
||||
state.lastUserParamIntent = userTextMentionsParams(text);
|
||||
state.lastUserControlIntent = userTextMentionsControls(text);
|
||||
@@ -8863,6 +8935,7 @@ ${HELP_TEXT}`);
|
||||
}
|
||||
const chatEpoch = bumpChatEpoch();
|
||||
state.busy = true;
|
||||
state.turnSettled = false;
|
||||
state.llmParked = false;
|
||||
setInterruptVisible(true);
|
||||
if (!isContinuationTurn(opts) && !opts.fromAskHop) {
|
||||
@@ -8997,6 +9070,11 @@ ${HELP_TEXT}`);
|
||||
if (chatEpoch !== state.chatEpoch) {
|
||||
return;
|
||||
}
|
||||
if (state.turnSettled) {
|
||||
return;
|
||||
}
|
||||
state.turnSettled = true;
|
||||
clearStreamStall();
|
||||
if (meta.system_chars != null) {
|
||||
state.lastSystemChars = Number(meta.system_chars) || 0;
|
||||
}
|
||||
@@ -9046,6 +9124,11 @@ ${HELP_TEXT}`);
|
||||
if (chatEpoch !== state.chatEpoch) {
|
||||
return;
|
||||
}
|
||||
if (state.turnSettled) {
|
||||
return;
|
||||
}
|
||||
state.turnSettled = true;
|
||||
clearStreamStall();
|
||||
state.busy = false;
|
||||
setInterruptVisible(state.generating);
|
||||
stopBusyUi(msg);
|
||||
@@ -9061,6 +9144,15 @@ ${HELP_TEXT}`);
|
||||
};
|
||||
if (typeof makeWSRequest === "function") {
|
||||
beginStreamMessage(msgMeta);
|
||||
armStreamStall(chatEpoch, (reply) => {
|
||||
if (state.turnSettled || chatEpoch !== state.chatEpoch) {
|
||||
return;
|
||||
}
|
||||
if (state.streamEl) {
|
||||
finalizeStreamMessage(reply, []);
|
||||
}
|
||||
finishOk(reply, [], {});
|
||||
});
|
||||
makeWSRequest(
|
||||
"AssistentChatWS",
|
||||
payload,
|
||||
@@ -9091,12 +9183,16 @@ ${HELP_TEXT}`);
|
||||
}
|
||||
if (data.delta) {
|
||||
appendStreamDelta(data.delta);
|
||||
return;
|
||||
}
|
||||
if (data.done || data.reply != null) {
|
||||
if (state.turnSettled) {
|
||||
return;
|
||||
}
|
||||
const reply = data.reply || state.streamEl?.querySelector(".sa-msg-body")?.textContent || "";
|
||||
const civitai = data.civitai_results || [];
|
||||
finalizeStreamMessage(reply, civitai);
|
||||
if (state.streamEl) {
|
||||
finalizeStreamMessage(reply, civitai);
|
||||
}
|
||||
finishOk(reply, civitai, {
|
||||
system_chars: data.system_chars,
|
||||
system_layers: data.system_layers,
|
||||
@@ -9607,7 +9703,6 @@ ${HELP_TEXT}`);
|
||||
$2("sa_btn_build_gen")?.addEventListener("click", () => buildCurrentAndGenerate());
|
||||
$2("sa_btn_interrupt")?.addEventListener("click", () => {
|
||||
doInterruptNow();
|
||||
clearInFlightUi({ status: "\u041F\u0440\u0435\u0440\u0432\u0430\u043D\u043E" });
|
||||
});
|
||||
$2("sa_btn_clear")?.addEventListener("click", () => {
|
||||
if (window.confirm("\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0432\u0435\u0441\u044C \u0447\u0430\u0442 Assistent?")) {
|
||||
|
||||
@@ -870,13 +870,15 @@
|
||||
.sa-dots i:nth-child(2) { animation-delay: 0.15s; }
|
||||
.sa-dots i:nth-child(3) { animation-delay: 0.3s; }
|
||||
|
||||
.sa-is-busy .sa-primary {
|
||||
.sa-is-busy .sa-primary:not(#sa_btn_send) {
|
||||
opacity: 0.55;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sa-is-busy #sa_input,
|
||||
.sa-input-busy {
|
||||
opacity: 0.7;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.sa-composer-busy {
|
||||
|
||||
Reference in New Issue
Block a user