Show live and historical turn activity trace (model, pack, steps) (0.15.17).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+328
-56
@@ -1040,10 +1040,179 @@
|
|||||||
park: "\u25BC",
|
park: "\u25BC",
|
||||||
inventory: "\u25A4",
|
inventory: "\u25A4",
|
||||||
compress: "\u25A4",
|
compress: "\u25A4",
|
||||||
|
ctx: "\u2699",
|
||||||
|
model: "\u25C9",
|
||||||
done: "\u2713",
|
done: "\u2713",
|
||||||
skip: "\u2013",
|
skip: "\u2013",
|
||||||
error: "!"
|
error: "!"
|
||||||
};
|
};
|
||||||
|
function trimDetail(s, max = 220) {
|
||||||
|
const t = String(s || "").trim();
|
||||||
|
if (t.length <= max) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
return `${t.slice(0, max)}\u2026`;
|
||||||
|
}
|
||||||
|
function shortModelName(model) {
|
||||||
|
const s = String(model || "").trim();
|
||||||
|
if (!s) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const slash = s.lastIndexOf("/");
|
||||||
|
return slash >= 0 ? s.slice(slash + 1) : s;
|
||||||
|
}
|
||||||
|
function buildContextMeta(context = {}) {
|
||||||
|
const parts = [];
|
||||||
|
const model = shortModelName(context.model);
|
||||||
|
if (model) {
|
||||||
|
parts.push(model);
|
||||||
|
}
|
||||||
|
if (context.pack) {
|
||||||
|
parts.push(String(context.pack).replace(/_/g, " "));
|
||||||
|
}
|
||||||
|
if (context.persona && context.persona !== "neutral") {
|
||||||
|
parts.push(String(context.persona));
|
||||||
|
}
|
||||||
|
if (Array.isArray(context.skills) && context.skills.length) {
|
||||||
|
parts.push(`skills: ${context.skills.slice(0, 4).join(", ")}`);
|
||||||
|
}
|
||||||
|
if (context.hop) {
|
||||||
|
parts.push(String(context.hop));
|
||||||
|
}
|
||||||
|
return parts.join(" \xB7 ");
|
||||||
|
}
|
||||||
|
function renderMetaRow(context) {
|
||||||
|
const meta = buildContextMeta(context);
|
||||||
|
if (!meta) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const row = document.createElement("div");
|
||||||
|
row.className = "sa-activity-meta";
|
||||||
|
row.textContent = meta;
|
||||||
|
row.title = meta;
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
function renderSteps(listEl, steps) {
|
||||||
|
if (!listEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
listEl.replaceChildren(...steps.map(renderStep));
|
||||||
|
}
|
||||||
|
function renderStep(step) {
|
||||||
|
const row = document.createElement("div");
|
||||||
|
row.className = `sa-activity-step sa-activity-${step.status || "done"}`;
|
||||||
|
row.dataset.id = step.id;
|
||||||
|
const icon = document.createElement("span");
|
||||||
|
icon.className = "sa-activity-icon";
|
||||||
|
icon.setAttribute("aria-hidden", "true");
|
||||||
|
icon.textContent = STEP_ICONS[step.kind] || STEP_ICONS.think;
|
||||||
|
const body = document.createElement("div");
|
||||||
|
body.className = "sa-activity-body";
|
||||||
|
const label = document.createElement("div");
|
||||||
|
label.className = "sa-activity-label";
|
||||||
|
label.textContent = step.label || step.id;
|
||||||
|
body.appendChild(label);
|
||||||
|
if (step.detail) {
|
||||||
|
const detail = document.createElement("div");
|
||||||
|
detail.className = "sa-activity-detail";
|
||||||
|
detail.textContent = step.detail;
|
||||||
|
body.appendChild(detail);
|
||||||
|
}
|
||||||
|
row.appendChild(icon);
|
||||||
|
row.appendChild(body);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
function buildActivityCard(trace, { live = false, collapsed = false } = {}) {
|
||||||
|
const card = document.createElement("div");
|
||||||
|
card.className = "sa-activity" + (live ? " sa-activity-live" : " sa-activity-done sa-activity-historical");
|
||||||
|
const steps = Array.isArray(trace?.steps) ? trace.steps : [];
|
||||||
|
const running = steps.some((s) => s.status === "running");
|
||||||
|
if (!live && !running) {
|
||||||
|
card.classList.add("sa-activity-done");
|
||||||
|
}
|
||||||
|
if (running) {
|
||||||
|
card.classList.add("sa-activity-live");
|
||||||
|
}
|
||||||
|
card.setAttribute("role", "status");
|
||||||
|
card.setAttribute("aria-live", live ? "polite" : "off");
|
||||||
|
const head = document.createElement("button");
|
||||||
|
head.type = "button";
|
||||||
|
head.className = "sa-activity-head";
|
||||||
|
head.setAttribute("aria-expanded", collapsed ? "false" : "true");
|
||||||
|
const spin = document.createElement("span");
|
||||||
|
spin.className = "sa-activity-spin";
|
||||||
|
spin.setAttribute("aria-hidden", "true");
|
||||||
|
const titleEl = document.createElement("span");
|
||||||
|
titleEl.className = "sa-activity-title";
|
||||||
|
const runningStep = steps.find((s) => s.status === "running");
|
||||||
|
const last = steps[steps.length - 1];
|
||||||
|
titleEl.textContent = trace?.title || runningStep?.label || last?.label || "Assistent";
|
||||||
|
const chev = document.createElement("span");
|
||||||
|
chev.className = "sa-activity-chev";
|
||||||
|
chev.setAttribute("aria-hidden", "true");
|
||||||
|
chev.textContent = "\u25BE";
|
||||||
|
head.appendChild(spin);
|
||||||
|
head.appendChild(titleEl);
|
||||||
|
head.appendChild(chev);
|
||||||
|
let open = !collapsed;
|
||||||
|
head.addEventListener("click", () => {
|
||||||
|
open = !open;
|
||||||
|
card.classList.toggle("sa-activity-collapsed", !open);
|
||||||
|
head.setAttribute("aria-expanded", open ? "true" : "false");
|
||||||
|
});
|
||||||
|
if (collapsed) {
|
||||||
|
card.classList.add("sa-activity-collapsed");
|
||||||
|
}
|
||||||
|
const meta = renderMetaRow(trace?.context || {});
|
||||||
|
const listEl = document.createElement("div");
|
||||||
|
listEl.className = "sa-activity-steps";
|
||||||
|
renderSteps(listEl, steps);
|
||||||
|
card.appendChild(head);
|
||||||
|
if (meta) {
|
||||||
|
card.appendChild(meta);
|
||||||
|
}
|
||||||
|
card.appendChild(listEl);
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
function mountActivityTrace(trace, { box, insertBefore = null, collapsed = true } = {}) {
|
||||||
|
const host = box || null;
|
||||||
|
if (!host || !trace || !Array.isArray(trace.steps) || !trace.steps.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const card = buildActivityCard(trace, { live: false, collapsed });
|
||||||
|
if (insertBefore && insertBefore.parentNode === host) {
|
||||||
|
host.insertBefore(card, insertBefore);
|
||||||
|
} else {
|
||||||
|
host.appendChild(card);
|
||||||
|
}
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
function slimActivityTrace(trace) {
|
||||||
|
if (!trace || typeof trace !== "object") {
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
const steps = (trace.steps || []).slice(0, 32).map((s) => ({
|
||||||
|
id: String(s.id || "").slice(0, 40),
|
||||||
|
kind: s.kind || "think",
|
||||||
|
label: String(s.label || s.id || "").slice(0, 120),
|
||||||
|
detail: trimDetail(s.detail, 180),
|
||||||
|
status: s.status || "done"
|
||||||
|
}));
|
||||||
|
if (!steps.length) {
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: String(trace.title || "Assistent").slice(0, 80),
|
||||||
|
context: trace.context ? {
|
||||||
|
model: trace.context.model || void 0,
|
||||||
|
pack: trace.context.pack || void 0,
|
||||||
|
persona: trace.context.persona || void 0,
|
||||||
|
skills: Array.isArray(trace.context.skills) ? trace.context.skills.slice(0, 8) : void 0,
|
||||||
|
hop: trace.context.hop || void 0
|
||||||
|
} : void 0,
|
||||||
|
steps
|
||||||
|
};
|
||||||
|
}
|
||||||
function createActivityController(opts = {}) {
|
function createActivityController(opts = {}) {
|
||||||
const {
|
const {
|
||||||
getMessagesEl,
|
getMessagesEl,
|
||||||
@@ -1053,8 +1222,27 @@
|
|||||||
let card = null;
|
let card = null;
|
||||||
let listEl = null;
|
let listEl = null;
|
||||||
let titleEl = null;
|
let titleEl = null;
|
||||||
|
let metaEl = null;
|
||||||
let steps = [];
|
let steps = [];
|
||||||
|
let context = {};
|
||||||
|
let title = "Assistent";
|
||||||
let open = true;
|
let open = true;
|
||||||
|
function insertCard(el) {
|
||||||
|
const box = typeof getMessagesEl === "function" ? getMessagesEl() : null;
|
||||||
|
if (!box || !el) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lastUser = box.querySelector(".sa-msg.user:last-of-type");
|
||||||
|
if (lastUser && lastUser.parentNode === box) {
|
||||||
|
if (lastUser.nextSibling) {
|
||||||
|
box.insertBefore(el, lastUser.nextSibling);
|
||||||
|
} else {
|
||||||
|
box.appendChild(el);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
box.appendChild(el);
|
||||||
|
}
|
||||||
function ensureCard() {
|
function ensureCard() {
|
||||||
const box = typeof getMessagesEl === "function" ? getMessagesEl() : null;
|
const box = typeof getMessagesEl === "function" ? getMessagesEl() : null;
|
||||||
if (!box) {
|
if (!box) {
|
||||||
@@ -1079,7 +1267,7 @@
|
|||||||
spin.setAttribute("aria-hidden", "true");
|
spin.setAttribute("aria-hidden", "true");
|
||||||
titleEl = document.createElement("span");
|
titleEl = document.createElement("span");
|
||||||
titleEl.className = "sa-activity-title";
|
titleEl.className = "sa-activity-title";
|
||||||
titleEl.textContent = "Assistent";
|
titleEl.textContent = title || "Assistent";
|
||||||
const chev = document.createElement("span");
|
const chev = document.createElement("span");
|
||||||
chev.className = "sa-activity-chev";
|
chev.className = "sa-activity-chev";
|
||||||
chev.setAttribute("aria-hidden", "true");
|
chev.setAttribute("aria-hidden", "true");
|
||||||
@@ -1095,46 +1283,32 @@
|
|||||||
listEl = document.createElement("div");
|
listEl = document.createElement("div");
|
||||||
listEl.className = "sa-activity-steps";
|
listEl.className = "sa-activity-steps";
|
||||||
card.appendChild(head);
|
card.appendChild(head);
|
||||||
|
metaEl = renderMetaRow(context);
|
||||||
|
if (metaEl) {
|
||||||
|
card.appendChild(metaEl);
|
||||||
|
}
|
||||||
card.appendChild(listEl);
|
card.appendChild(listEl);
|
||||||
box.appendChild(card);
|
insertCard(card);
|
||||||
if (typeof scrollToBottom === "function") {
|
if (typeof scrollToBottom === "function") {
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
function renderStep(step) {
|
|
||||||
const row = document.createElement("div");
|
|
||||||
row.className = `sa-activity-step sa-activity-${step.status || "running"}`;
|
|
||||||
row.dataset.id = step.id;
|
|
||||||
const icon = document.createElement("span");
|
|
||||||
icon.className = "sa-activity-icon";
|
|
||||||
icon.setAttribute("aria-hidden", "true");
|
|
||||||
icon.textContent = STEP_ICONS[step.kind] || STEP_ICONS.think;
|
|
||||||
const body = document.createElement("div");
|
|
||||||
body.className = "sa-activity-body";
|
|
||||||
const label = document.createElement("div");
|
|
||||||
label.className = "sa-activity-label";
|
|
||||||
label.textContent = step.label || step.id;
|
|
||||||
body.appendChild(label);
|
|
||||||
if (step.detail) {
|
|
||||||
const detail = document.createElement("div");
|
|
||||||
detail.className = "sa-activity-detail";
|
|
||||||
detail.textContent = step.detail;
|
|
||||||
body.appendChild(detail);
|
|
||||||
}
|
|
||||||
row.appendChild(icon);
|
|
||||||
row.appendChild(body);
|
|
||||||
return row;
|
|
||||||
}
|
|
||||||
function paint() {
|
function paint() {
|
||||||
if (!ensureCard() || !listEl) {
|
if (!ensureCard() || !listEl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
listEl.replaceChildren(...steps.map(renderStep));
|
if (metaEl) {
|
||||||
|
const nextMeta = buildContextMeta(context);
|
||||||
|
metaEl.textContent = nextMeta;
|
||||||
|
metaEl.title = nextMeta;
|
||||||
|
metaEl.hidden = !nextMeta;
|
||||||
|
}
|
||||||
|
renderSteps(listEl, steps);
|
||||||
const running = steps.find((s) => s.status === "running");
|
const running = steps.find((s) => s.status === "running");
|
||||||
const last = steps[steps.length - 1];
|
const last = steps[steps.length - 1];
|
||||||
if (titleEl) {
|
if (titleEl) {
|
||||||
titleEl.textContent = running ? running.label : last?.label || "Assistent";
|
titleEl.textContent = running ? running.label : last?.label || title || "Assistent";
|
||||||
}
|
}
|
||||||
card.classList.toggle("sa-activity-live", steps.some((s) => s.status === "running"));
|
card.classList.toggle("sa-activity-live", steps.some((s) => s.status === "running"));
|
||||||
card.classList.toggle("sa-activity-done", steps.length > 0 && steps.every((s) => s.status === "done" || s.status === "skip"));
|
card.classList.toggle("sa-activity-done", steps.length > 0 && steps.every((s) => s.status === "done" || s.status === "skip"));
|
||||||
@@ -1142,15 +1316,44 @@
|
|||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function begin(title) {
|
function setContext(patch = {}) {
|
||||||
|
context = { ...context, ...patch };
|
||||||
|
if (card && !metaEl) {
|
||||||
|
metaEl = renderMetaRow(context);
|
||||||
|
if (metaEl && card.firstChild) {
|
||||||
|
card.insertBefore(metaEl, card.firstChild);
|
||||||
|
} else if (metaEl) {
|
||||||
|
card.prepend(metaEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paint();
|
||||||
|
}
|
||||||
|
function begin(nextTitle, nextContext = {}) {
|
||||||
steps = [];
|
steps = [];
|
||||||
card = null;
|
card = null;
|
||||||
listEl = null;
|
listEl = null;
|
||||||
titleEl = null;
|
titleEl = null;
|
||||||
|
metaEl = null;
|
||||||
open = true;
|
open = true;
|
||||||
|
title = nextTitle || "Assistent";
|
||||||
|
context = nextContext && typeof nextContext === "object" ? { ...nextContext } : {};
|
||||||
ensureCard();
|
ensureCard();
|
||||||
if (titleEl && title) {
|
const meta = buildContextMeta(context);
|
||||||
titleEl.textContent = title;
|
if (meta) {
|
||||||
|
upsert("ctx", {
|
||||||
|
kind: "ctx",
|
||||||
|
label: "\u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u0445\u043E\u0434\u0430",
|
||||||
|
detail: meta,
|
||||||
|
status: "done"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (context.model) {
|
||||||
|
upsert("model", {
|
||||||
|
kind: "model",
|
||||||
|
label: "\u041C\u043E\u0434\u0435\u043B\u044C",
|
||||||
|
detail: String(context.model),
|
||||||
|
status: "done"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
paint();
|
paint();
|
||||||
}
|
}
|
||||||
@@ -1183,6 +1386,9 @@
|
|||||||
s.status = "done";
|
s.status = "done";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (summary) {
|
||||||
|
title = summary;
|
||||||
|
}
|
||||||
if (summary && titleEl) {
|
if (summary && titleEl) {
|
||||||
titleEl.textContent = summary;
|
titleEl.textContent = summary;
|
||||||
}
|
}
|
||||||
@@ -1192,6 +1398,13 @@
|
|||||||
card.classList.add("sa-activity-done");
|
card.classList.add("sa-activity-done");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function snapshot() {
|
||||||
|
return slimActivityTrace({
|
||||||
|
title: titleEl?.textContent || title || "Assistent",
|
||||||
|
context,
|
||||||
|
steps: steps.slice()
|
||||||
|
});
|
||||||
|
}
|
||||||
function noteModelCommands(patch) {
|
function noteModelCommands(patch) {
|
||||||
if (!patch || typeof patch !== "object") {
|
if (!patch || typeof patch !== "object") {
|
||||||
return;
|
return;
|
||||||
@@ -1200,16 +1413,16 @@
|
|||||||
if (keys.length) {
|
if (keys.length) {
|
||||||
done("delta", {
|
done("delta", {
|
||||||
kind: "delta",
|
kind: "delta",
|
||||||
label: "\u041E\u0431\u043D\u043E\u0432\u0438\u043B \u0441\u0435\u0441\u0441\u0438\u044E",
|
label: "\u041F\u0430\u0442\u0447 \u0441\u0435\u0441\u0441\u0438\u0438",
|
||||||
detail: keys.slice(0, 10).join(", ")
|
detail: keys.slice(0, 12).join(", ")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const ask = Array.isArray(patch.ask) ? patch.ask.map(String) : patch.ask ? [String(patch.ask)] : [];
|
const ask = Array.isArray(patch.ask) ? patch.ask.map(String) : patch.ask ? [String(patch.ask)] : [];
|
||||||
if (ask.length) {
|
if (ask.length) {
|
||||||
upsert("ask", {
|
upsert("ask", {
|
||||||
kind: "ask",
|
kind: "ask",
|
||||||
label: `\u0417\u0430\u043F\u0440\u043E\u0441\u0438\u043B ${ask.join(", ")}`,
|
label: `ask: ${ask.join(", ")}`,
|
||||||
detail: "\u043F\u043E\u0434\u0433\u0440\u0443\u0436\u0430\u044E \u0434\u0435\u0442\u0430\u043B\u0438\u2026",
|
detail: "\u0437\u0430\u043F\u0440\u043E\u0441 \u0434\u0435\u0442\u0430\u043B\u0435\u0439 \u0443 SwarmUI",
|
||||||
status: "running"
|
status: "running"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1217,7 +1430,7 @@
|
|||||||
const slots = [].concat(patch.look_at || patch.vision_from || patch.vision_slots || []);
|
const slots = [].concat(patch.look_at || patch.vision_from || patch.vision_slots || []);
|
||||||
upsert("look", {
|
upsert("look", {
|
||||||
kind: "look",
|
kind: "look",
|
||||||
label: "\u0421\u043C\u043E\u0442\u0440\u0438\u0442 \u043D\u0430 \u043A\u0430\u0434\u0440",
|
label: "look_at",
|
||||||
detail: slots.map(String).slice(0, 4).join(", "),
|
detail: slots.map(String).slice(0, 4).join(", "),
|
||||||
status: "running"
|
status: "running"
|
||||||
});
|
});
|
||||||
@@ -1226,25 +1439,27 @@
|
|||||||
upsert("generate", {
|
upsert("generate", {
|
||||||
kind: "generate",
|
kind: "generate",
|
||||||
label: "Generate",
|
label: "Generate",
|
||||||
detail: "\u0436\u0434\u0451\u0442 \u043F\u0430\u0439\u043F\u043B\u0430\u0439\u043D\u2026",
|
detail: "\u043A\u043B\u0438\u0435\u043D\u0442 \u2192 SwarmUI",
|
||||||
status: "running"
|
status: "running"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (Array.isArray(patch.variants) && patch.variants.length) {
|
if (Array.isArray(patch.variants) && patch.variants.length) {
|
||||||
upsert("variants", {
|
upsert("variants", {
|
||||||
kind: "generate",
|
kind: "generate",
|
||||||
label: `\u0412\u0430\u0440\u0438\u0430\u043D\u0442\u044B \xD7${patch.variants.length}`,
|
label: `variants \xD7${patch.variants.length}`,
|
||||||
status: "running"
|
status: "running"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
begin,
|
begin,
|
||||||
|
setContext,
|
||||||
upsert,
|
upsert,
|
||||||
done,
|
done,
|
||||||
skip,
|
skip,
|
||||||
fail,
|
fail,
|
||||||
finish,
|
finish,
|
||||||
|
snapshot,
|
||||||
noteModelCommands,
|
noteModelCommands,
|
||||||
get steps() {
|
get steps() {
|
||||||
return steps.slice();
|
return steps.slice();
|
||||||
@@ -1253,6 +1468,8 @@
|
|||||||
}
|
}
|
||||||
function attachActivity(SA2) {
|
function attachActivity(SA2) {
|
||||||
SA2.createActivityController = createActivityController;
|
SA2.createActivityController = createActivityController;
|
||||||
|
SA2.mountActivityTrace = mountActivityTrace;
|
||||||
|
SA2.slimActivityTrace = slimActivityTrace;
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/kreaProfile.js
|
// src/kreaProfile.js
|
||||||
@@ -1566,7 +1783,8 @@
|
|||||||
settingsPersonaId: null,
|
settingsPersonaId: null,
|
||||||
ollamaHealth: "unknown",
|
ollamaHealth: "unknown",
|
||||||
trainingLock: false,
|
trainingLock: false,
|
||||||
activity: null
|
activity: null,
|
||||||
|
pendingActivityTrace: null
|
||||||
};
|
};
|
||||||
function getActivity() {
|
function getActivity() {
|
||||||
if (state.activity) {
|
if (state.activity) {
|
||||||
@@ -1581,12 +1799,24 @@
|
|||||||
}
|
}
|
||||||
return state.activity;
|
return state.activity;
|
||||||
}
|
}
|
||||||
function activityBegin(title) {
|
function activityBegin(title, context) {
|
||||||
const a = getActivity();
|
const a = getActivity();
|
||||||
if (a) {
|
if (a) {
|
||||||
a.begin(title || "Assistent");
|
a.begin(title || "Assistent", context || {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function captureActivityTrace() {
|
||||||
|
const a = getActivity();
|
||||||
|
if (!a || typeof a.snapshot !== "function") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const snap = a.snapshot();
|
||||||
|
if (snap && snap.steps && snap.steps.length) {
|
||||||
|
state.pendingActivityTrace = snap;
|
||||||
|
return snap;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
function activityStep(id, patch) {
|
function activityStep(id, patch) {
|
||||||
const a = getActivity();
|
const a = getActivity();
|
||||||
if (a) {
|
if (a) {
|
||||||
@@ -1603,6 +1833,7 @@
|
|||||||
const a = getActivity();
|
const a = getActivity();
|
||||||
if (a) {
|
if (a) {
|
||||||
a.finish(summary);
|
a.finish(summary);
|
||||||
|
captureActivityTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const HOP_BUDGET = 4;
|
const HOP_BUDGET = 4;
|
||||||
@@ -4364,7 +4595,8 @@ ${patch.prompt}`;
|
|||||||
role: m.role,
|
role: m.role,
|
||||||
content: content.slice(0, 4e3),
|
content: content.slice(0, 4e3),
|
||||||
persona: m.persona || void 0,
|
persona: m.persona || void 0,
|
||||||
pack: m.pack || void 0
|
pack: m.pack || void 0,
|
||||||
|
activity: window.SA && typeof SA.slimActivityTrace === "function" ? SA.slimActivityTrace(m.activity) : m.activity || void 0
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -4725,7 +4957,8 @@ ${patch.prompt}`;
|
|||||||
appendMessage("assistant", m.content, null, null, {
|
appendMessage("assistant", m.content, null, null, {
|
||||||
persona: m.persona ? { id: m.persona, title: m.persona } : null,
|
persona: m.persona ? { id: m.persona, title: m.persona } : null,
|
||||||
pack: m.pack,
|
pack: m.pack,
|
||||||
historical: true
|
historical: true,
|
||||||
|
activity: m.activity
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6162,7 +6395,14 @@ ${patch.prompt}`;
|
|||||||
} else {
|
} else {
|
||||||
appendMessage("assistant", prose);
|
appendMessage("assistant", prose);
|
||||||
}
|
}
|
||||||
state.history.push({ role: "assistant", content: prose, persona, pack });
|
state.history.push({
|
||||||
|
role: "assistant",
|
||||||
|
content: prose,
|
||||||
|
persona,
|
||||||
|
pack,
|
||||||
|
activity: captureActivityTrace() || state.pendingActivityTrace || void 0
|
||||||
|
});
|
||||||
|
state.pendingActivityTrace = null;
|
||||||
persistHistory();
|
persistHistory();
|
||||||
state.turnSettled = true;
|
state.turnSettled = true;
|
||||||
}
|
}
|
||||||
@@ -6711,6 +6951,9 @@ ${patch.prompt}`;
|
|||||||
if (role === "assistant" && !(meta && meta.historical)) {
|
if (role === "assistant" && !(meta && meta.historical)) {
|
||||||
mountCurateButtons(div, meta);
|
mountCurateButtons(div, meta);
|
||||||
}
|
}
|
||||||
|
if (role === "assistant" && meta?.historical && meta?.activity && window.SA && typeof SA.mountActivityTrace === "function") {
|
||||||
|
SA.mountActivityTrace(meta.activity, { box, insertBefore: div, collapsed: true });
|
||||||
|
}
|
||||||
box.appendChild(div);
|
box.appendChild(div);
|
||||||
scrollMessagesToBottom({ force: true });
|
scrollMessagesToBottom({ force: true });
|
||||||
return div;
|
return div;
|
||||||
@@ -9725,16 +9968,6 @@ ${HELP_TEXT}`);
|
|||||||
state.turnSettled = false;
|
state.turnSettled = false;
|
||||||
state.llmParked = false;
|
state.llmParked = false;
|
||||||
setInterruptVisible(true);
|
setInterruptVisible(true);
|
||||||
if (!isContinuationTurn(opts) && !opts.fromAskHop) {
|
|
||||||
activityBegin(opts.fromDebug ? "Debug" : "\u0425\u043E\u0434 Assistent");
|
|
||||||
activityStep("think", { kind: "think", label: "\u0414\u0443\u043C\u0430\u044E\u2026", status: "running" });
|
|
||||||
} else if (opts.fromAskHop) {
|
|
||||||
activityStep("think", { kind: "think", label: "\u041E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0441 \u0434\u0435\u0442\u0430\u043B\u044F\u043C\u0438\u2026", status: "running" });
|
|
||||||
} else if (opts.fromVisionHop) {
|
|
||||||
activityStep("look", { kind: "look", label: "\u0421\u043C\u043E\u0442\u0440\u0438\u0442 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435\u2026", status: "running" });
|
|
||||||
} else if (opts.fromPromptEnRetry) {
|
|
||||||
activityStep("prep", { kind: "prep", label: "\u0414\u043E\u043F\u0438\u0441\u044B\u0432\u0430\u044E EN-\u043F\u0440\u043E\u043C\u043F\u0442\u2026", status: "running" });
|
|
||||||
}
|
|
||||||
if (state.expectColdLoad && !isContinuationTurn(opts)) {
|
if (state.expectColdLoad && !isContinuationTurn(opts)) {
|
||||||
startBusyUi("warming");
|
startBusyUi("warming");
|
||||||
setStatus("\u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u044E LLM \u0432 GPU\u2026");
|
setStatus("\u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u044E LLM \u0432 GPU\u2026");
|
||||||
@@ -9811,6 +10044,27 @@ ${HELP_TEXT}`);
|
|||||||
persistHistory();
|
persistHistory();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!isContinuationTurn(opts) && !opts.fromAskHop) {
|
||||||
|
state.pendingActivityTrace = null;
|
||||||
|
const hopLabel = opts.fromVisionHop ? "vision hop" : opts.fromPromptEnRetry ? "krea prep" : opts.fromAutoCritique ? "critique" : opts.fromDebug ? "debug" : "";
|
||||||
|
activityBegin(opts.fromDebug ? "Debug" : "\u0425\u043E\u0434 Assistent", {
|
||||||
|
model,
|
||||||
|
pack,
|
||||||
|
persona,
|
||||||
|
skills: opts.fromDebug ? [] : state.enabledSkills || [],
|
||||||
|
hop: hopLabel || void 0
|
||||||
|
});
|
||||||
|
activityStep("think", { kind: "think", label: "\u0414\u0443\u043C\u0430\u044E\u2026", status: "running" });
|
||||||
|
} else if (opts.fromAskHop) {
|
||||||
|
getActivity()?.setContext?.({ hop: "ask hop", pack, model });
|
||||||
|
activityStep("think", { kind: "think", label: "\u041E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0441 \u0434\u0435\u0442\u0430\u043B\u044F\u043C\u0438\u2026", status: "running" });
|
||||||
|
} else if (opts.fromVisionHop) {
|
||||||
|
getActivity()?.setContext?.({ hop: "vision hop" });
|
||||||
|
activityStep("look", { kind: "look", label: "\u0421\u043C\u043E\u0442\u0440\u0438\u0442 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435\u2026", status: "running" });
|
||||||
|
} else if (opts.fromPromptEnRetry) {
|
||||||
|
getActivity()?.setContext?.({ hop: "krea prep" });
|
||||||
|
activityStep("prep", { kind: "prep", label: "\u0414\u043E\u043F\u0438\u0441\u044B\u0432\u0430\u044E EN-\u043F\u0440\u043E\u043C\u043F\u0442\u2026", status: "running" });
|
||||||
|
}
|
||||||
if (!opts.fromDebug && !opts.fromCompress && !opts.fromAskHop && !opts.fromVisionHop && !opts.fromAutoCritique && !isContinuationTurn(opts)) {
|
if (!opts.fromDebug && !opts.fromCompress && !opts.fromAskHop && !opts.fromVisionHop && !opts.fromAutoCritique && !isContinuationTurn(opts)) {
|
||||||
try {
|
try {
|
||||||
await maybeAutoCompressBeforeSend(chatEpoch);
|
await maybeAutoCompressBeforeSend(chatEpoch);
|
||||||
@@ -9885,8 +10139,6 @@ ${HELP_TEXT}`);
|
|||||||
}
|
}
|
||||||
updateCtxChip();
|
updateCtxChip();
|
||||||
const prose = visibleAssistantProse(reply);
|
const prose = visibleAssistantProse(reply);
|
||||||
state.history.push({ role: "assistant", content: prose, persona, pack });
|
|
||||||
persistHistory();
|
|
||||||
setBusyPhase(state.pendingSilentGen ? "silent_gen" : "thinking");
|
setBusyPhase(state.pendingSilentGen ? "silent_gen" : "thinking");
|
||||||
try {
|
try {
|
||||||
await handleReplySideEffects(reply, civitaiResults, {
|
await handleReplySideEffects(reply, civitaiResults, {
|
||||||
@@ -9895,6 +10147,26 @@ ${HELP_TEXT}`);
|
|||||||
userWantsGenerate: !!opts.userWantsGenerate || !!state.turnUserWantsGenerate || !isMachineTurn(opts) && state.pendingSilentGen,
|
userWantsGenerate: !!opts.userWantsGenerate || !!state.turnUserWantsGenerate || !isMachineTurn(opts) && state.pendingSilentGen,
|
||||||
attachedSlotIds: visionSlots.map((s) => s.id)
|
attachedSlotIds: visionSlots.map((s) => s.id)
|
||||||
});
|
});
|
||||||
|
if (meta.system_layers && typeof meta.system_layers === "object") {
|
||||||
|
const layers = Object.keys(meta.system_layers).filter((k) => meta.system_layers[k]);
|
||||||
|
if (layers.length) {
|
||||||
|
activityDone("layers", {
|
||||||
|
kind: "ctx",
|
||||||
|
label: "system_layers",
|
||||||
|
detail: layers.slice(0, 12).join(", ")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
captureActivityTrace();
|
||||||
|
state.history.push({
|
||||||
|
role: "assistant",
|
||||||
|
content: prose,
|
||||||
|
persona,
|
||||||
|
pack,
|
||||||
|
activity: state.pendingActivityTrace || void 0
|
||||||
|
});
|
||||||
|
state.pendingActivityTrace = null;
|
||||||
|
persistHistory();
|
||||||
} finally {
|
} finally {
|
||||||
if (chatEpoch !== state.chatEpoch) {
|
if (chatEpoch !== state.chatEpoch) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -3081,6 +3081,24 @@
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sa-activity-meta {
|
||||||
|
padding: 0.15rem 0.55rem 0.25rem;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
opacity: 0.78;
|
||||||
|
word-break: break-word;
|
||||||
|
border-bottom: 1px solid color-mix(in srgb, currentColor 12%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-activity-historical {
|
||||||
|
margin: 0.25rem 0 0.35rem;
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-activity-historical .sa-activity-head {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes sa-spin {
|
@keyframes sa-spin {
|
||||||
to { transform: rotate(360deg); }
|
to { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public partial class SwarmAssistentExtension : Extension
|
|||||||
ExtensionAuthor = "mrleo1nid";
|
ExtensionAuthor = "mrleo1nid";
|
||||||
Description = "Collaborative Krea 2 assistant: Ollama chat, persona presets, vector memory, model cards, Generate loop.";
|
Description = "Collaborative Krea 2 assistant: Ollama chat, persona presets, vector memory, model cards, Generate loop.";
|
||||||
License = "MIT";
|
License = "MIT";
|
||||||
Version = "0.15.16";
|
Version = "0.15.17";
|
||||||
Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint", "memory", "training", "heard", "qlora"];
|
Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint", "memory", "training", "heard", "qlora"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node scripts/build.mjs",
|
"build": "node scripts/build.mjs",
|
||||||
"watch": "node scripts/build.mjs --watch",
|
"watch": "node scripts/build.mjs --watch",
|
||||||
"test": "node --test test/intent.test.js test/patch.test.js test/context.test.js test/kreaProfile.test.js test/aspect.test.js"
|
"test": "node --test test/intent.test.js test/patch.test.js test/context.test.js test/kreaProfile.test.js test/aspect.test.js test/activity.test.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"esbuild": "^0.25.0"
|
"esbuild": "^0.25.0"
|
||||||
|
|||||||
+286
-45
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Turn activity timeline — shows model commands and pipeline steps in chat.
|
* Turn activity timeline — shows model commands and pipeline steps in chat.
|
||||||
* Cursor-like, but compact and chat-native.
|
* Cursor-like, but compact and chat-native. Persists per assistant turn in history.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const STEP_ICONS = {
|
const STEP_ICONS = {
|
||||||
@@ -16,11 +16,207 @@ const STEP_ICONS = {
|
|||||||
park: '▼',
|
park: '▼',
|
||||||
inventory: '▤',
|
inventory: '▤',
|
||||||
compress: '▤',
|
compress: '▤',
|
||||||
|
ctx: '⚙',
|
||||||
|
model: '◉',
|
||||||
done: '✓',
|
done: '✓',
|
||||||
skip: '–',
|
skip: '–',
|
||||||
error: '!',
|
error: '!',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function trimDetail(s, max = 220) {
|
||||||
|
const t = String(s || '').trim();
|
||||||
|
if (t.length <= max) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
return `${t.slice(0, max)}…`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shortModelName(model) {
|
||||||
|
const s = String(model || '').trim();
|
||||||
|
if (!s) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const slash = s.lastIndexOf('/');
|
||||||
|
return slash >= 0 ? s.slice(slash + 1) : s;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildContextMeta(context = {}) {
|
||||||
|
const parts = [];
|
||||||
|
const model = shortModelName(context.model);
|
||||||
|
if (model) {
|
||||||
|
parts.push(model);
|
||||||
|
}
|
||||||
|
if (context.pack) {
|
||||||
|
parts.push(String(context.pack).replace(/_/g, ' '));
|
||||||
|
}
|
||||||
|
if (context.persona && context.persona !== 'neutral') {
|
||||||
|
parts.push(String(context.persona));
|
||||||
|
}
|
||||||
|
if (Array.isArray(context.skills) && context.skills.length) {
|
||||||
|
parts.push(`skills: ${context.skills.slice(0, 4).join(', ')}`);
|
||||||
|
}
|
||||||
|
if (context.hop) {
|
||||||
|
parts.push(String(context.hop));
|
||||||
|
}
|
||||||
|
return parts.join(' · ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMetaRow(context) {
|
||||||
|
const meta = buildContextMeta(context);
|
||||||
|
if (!meta) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const row = document.createElement('div');
|
||||||
|
row.className = 'sa-activity-meta';
|
||||||
|
row.textContent = meta;
|
||||||
|
row.title = meta;
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSteps(listEl, steps) {
|
||||||
|
if (!listEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
listEl.replaceChildren(...steps.map(renderStep));
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderStep(step) {
|
||||||
|
const row = document.createElement('div');
|
||||||
|
row.className = `sa-activity-step sa-activity-${step.status || 'done'}`;
|
||||||
|
row.dataset.id = step.id;
|
||||||
|
|
||||||
|
const icon = document.createElement('span');
|
||||||
|
icon.className = 'sa-activity-icon';
|
||||||
|
icon.setAttribute('aria-hidden', 'true');
|
||||||
|
icon.textContent = STEP_ICONS[step.kind] || STEP_ICONS.think;
|
||||||
|
|
||||||
|
const body = document.createElement('div');
|
||||||
|
body.className = 'sa-activity-body';
|
||||||
|
|
||||||
|
const label = document.createElement('div');
|
||||||
|
label.className = 'sa-activity-label';
|
||||||
|
label.textContent = step.label || step.id;
|
||||||
|
|
||||||
|
body.appendChild(label);
|
||||||
|
if (step.detail) {
|
||||||
|
const detail = document.createElement('div');
|
||||||
|
detail.className = 'sa-activity-detail';
|
||||||
|
detail.textContent = step.detail;
|
||||||
|
body.appendChild(detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
row.appendChild(icon);
|
||||||
|
row.appendChild(body);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildActivityCard(trace, { live = false, collapsed = false } = {}) {
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'sa-activity'
|
||||||
|
+ (live ? ' sa-activity-live' : ' sa-activity-done sa-activity-historical');
|
||||||
|
|
||||||
|
const steps = Array.isArray(trace?.steps) ? trace.steps : [];
|
||||||
|
const running = steps.some((s) => s.status === 'running');
|
||||||
|
if (!live && !running) {
|
||||||
|
card.classList.add('sa-activity-done');
|
||||||
|
}
|
||||||
|
if (running) {
|
||||||
|
card.classList.add('sa-activity-live');
|
||||||
|
}
|
||||||
|
card.setAttribute('role', 'status');
|
||||||
|
card.setAttribute('aria-live', live ? 'polite' : 'off');
|
||||||
|
|
||||||
|
const head = document.createElement('button');
|
||||||
|
head.type = 'button';
|
||||||
|
head.className = 'sa-activity-head';
|
||||||
|
head.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
|
||||||
|
|
||||||
|
const spin = document.createElement('span');
|
||||||
|
spin.className = 'sa-activity-spin';
|
||||||
|
spin.setAttribute('aria-hidden', 'true');
|
||||||
|
|
||||||
|
const titleEl = document.createElement('span');
|
||||||
|
titleEl.className = 'sa-activity-title';
|
||||||
|
const runningStep = steps.find((s) => s.status === 'running');
|
||||||
|
const last = steps[steps.length - 1];
|
||||||
|
titleEl.textContent = trace?.title
|
||||||
|
|| runningStep?.label
|
||||||
|
|| last?.label
|
||||||
|
|| 'Assistent';
|
||||||
|
|
||||||
|
const chev = document.createElement('span');
|
||||||
|
chev.className = 'sa-activity-chev';
|
||||||
|
chev.setAttribute('aria-hidden', 'true');
|
||||||
|
chev.textContent = '▾';
|
||||||
|
|
||||||
|
head.appendChild(spin);
|
||||||
|
head.appendChild(titleEl);
|
||||||
|
head.appendChild(chev);
|
||||||
|
|
||||||
|
let open = !collapsed;
|
||||||
|
head.addEventListener('click', () => {
|
||||||
|
open = !open;
|
||||||
|
card.classList.toggle('sa-activity-collapsed', !open);
|
||||||
|
head.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||||
|
});
|
||||||
|
if (collapsed) {
|
||||||
|
card.classList.add('sa-activity-collapsed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = renderMetaRow(trace?.context || {});
|
||||||
|
const listEl = document.createElement('div');
|
||||||
|
listEl.className = 'sa-activity-steps';
|
||||||
|
renderSteps(listEl, steps);
|
||||||
|
card.appendChild(head);
|
||||||
|
if (meta) {
|
||||||
|
card.appendChild(meta);
|
||||||
|
}
|
||||||
|
card.appendChild(listEl);
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Static activity card for chat history replay. */
|
||||||
|
export function mountActivityTrace(trace, { box, insertBefore = null, collapsed = true } = {}) {
|
||||||
|
const host = box || null;
|
||||||
|
if (!host || !trace || !Array.isArray(trace.steps) || !trace.steps.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const card = buildActivityCard(trace, { live: false, collapsed });
|
||||||
|
if (insertBefore && insertBefore.parentNode === host) {
|
||||||
|
host.insertBefore(card, insertBefore);
|
||||||
|
} else {
|
||||||
|
host.appendChild(card);
|
||||||
|
}
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function slimActivityTrace(trace) {
|
||||||
|
if (!trace || typeof trace !== 'object') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const steps = (trace.steps || []).slice(0, 32).map((s) => ({
|
||||||
|
id: String(s.id || '').slice(0, 40),
|
||||||
|
kind: s.kind || 'think',
|
||||||
|
label: String(s.label || s.id || '').slice(0, 120),
|
||||||
|
detail: trimDetail(s.detail, 180),
|
||||||
|
status: s.status || 'done',
|
||||||
|
}));
|
||||||
|
if (!steps.length) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: String(trace.title || 'Assistent').slice(0, 80),
|
||||||
|
context: trace.context ? {
|
||||||
|
model: trace.context.model || undefined,
|
||||||
|
pack: trace.context.pack || undefined,
|
||||||
|
persona: trace.context.persona || undefined,
|
||||||
|
skills: Array.isArray(trace.context.skills) ? trace.context.skills.slice(0, 8) : undefined,
|
||||||
|
hop: trace.context.hop || undefined,
|
||||||
|
} : undefined,
|
||||||
|
steps,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function createActivityController(opts = {}) {
|
export function createActivityController(opts = {}) {
|
||||||
const {
|
const {
|
||||||
getMessagesEl,
|
getMessagesEl,
|
||||||
@@ -31,9 +227,29 @@ export function createActivityController(opts = {}) {
|
|||||||
let card = null;
|
let card = null;
|
||||||
let listEl = null;
|
let listEl = null;
|
||||||
let titleEl = null;
|
let titleEl = null;
|
||||||
|
let metaEl = null;
|
||||||
let steps = [];
|
let steps = [];
|
||||||
|
let context = {};
|
||||||
|
let title = 'Assistent';
|
||||||
let open = true;
|
let open = true;
|
||||||
|
|
||||||
|
function insertCard(el) {
|
||||||
|
const box = typeof getMessagesEl === 'function' ? getMessagesEl() : null;
|
||||||
|
if (!box || !el) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lastUser = box.querySelector('.sa-msg.user:last-of-type');
|
||||||
|
if (lastUser && lastUser.parentNode === box) {
|
||||||
|
if (lastUser.nextSibling) {
|
||||||
|
box.insertBefore(el, lastUser.nextSibling);
|
||||||
|
} else {
|
||||||
|
box.appendChild(el);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
box.appendChild(el);
|
||||||
|
}
|
||||||
|
|
||||||
function ensureCard() {
|
function ensureCard() {
|
||||||
const box = typeof getMessagesEl === 'function' ? getMessagesEl() : null;
|
const box = typeof getMessagesEl === 'function' ? getMessagesEl() : null;
|
||||||
if (!box) {
|
if (!box) {
|
||||||
@@ -61,7 +277,7 @@ export function createActivityController(opts = {}) {
|
|||||||
|
|
||||||
titleEl = document.createElement('span');
|
titleEl = document.createElement('span');
|
||||||
titleEl.className = 'sa-activity-title';
|
titleEl.className = 'sa-activity-title';
|
||||||
titleEl.textContent = 'Assistent';
|
titleEl.textContent = title || 'Assistent';
|
||||||
|
|
||||||
const chev = document.createElement('span');
|
const chev = document.createElement('span');
|
||||||
chev.className = 'sa-activity-chev';
|
chev.className = 'sa-activity-chev';
|
||||||
@@ -81,55 +297,35 @@ export function createActivityController(opts = {}) {
|
|||||||
listEl.className = 'sa-activity-steps';
|
listEl.className = 'sa-activity-steps';
|
||||||
|
|
||||||
card.appendChild(head);
|
card.appendChild(head);
|
||||||
|
metaEl = renderMetaRow(context);
|
||||||
|
if (metaEl) {
|
||||||
|
card.appendChild(metaEl);
|
||||||
|
}
|
||||||
card.appendChild(listEl);
|
card.appendChild(listEl);
|
||||||
box.appendChild(card);
|
insertCard(card);
|
||||||
if (typeof scrollToBottom === 'function') {
|
if (typeof scrollToBottom === 'function') {
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderStep(step) {
|
|
||||||
const row = document.createElement('div');
|
|
||||||
row.className = `sa-activity-step sa-activity-${step.status || 'running'}`;
|
|
||||||
row.dataset.id = step.id;
|
|
||||||
|
|
||||||
const icon = document.createElement('span');
|
|
||||||
icon.className = 'sa-activity-icon';
|
|
||||||
icon.setAttribute('aria-hidden', 'true');
|
|
||||||
icon.textContent = STEP_ICONS[step.kind] || STEP_ICONS.think;
|
|
||||||
|
|
||||||
const body = document.createElement('div');
|
|
||||||
body.className = 'sa-activity-body';
|
|
||||||
|
|
||||||
const label = document.createElement('div');
|
|
||||||
label.className = 'sa-activity-label';
|
|
||||||
label.textContent = step.label || step.id;
|
|
||||||
|
|
||||||
body.appendChild(label);
|
|
||||||
if (step.detail) {
|
|
||||||
const detail = document.createElement('div');
|
|
||||||
detail.className = 'sa-activity-detail';
|
|
||||||
detail.textContent = step.detail;
|
|
||||||
body.appendChild(detail);
|
|
||||||
}
|
|
||||||
|
|
||||||
row.appendChild(icon);
|
|
||||||
row.appendChild(body);
|
|
||||||
return row;
|
|
||||||
}
|
|
||||||
|
|
||||||
function paint() {
|
function paint() {
|
||||||
if (!ensureCard() || !listEl) {
|
if (!ensureCard() || !listEl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
listEl.replaceChildren(...steps.map(renderStep));
|
if (metaEl) {
|
||||||
|
const nextMeta = buildContextMeta(context);
|
||||||
|
metaEl.textContent = nextMeta;
|
||||||
|
metaEl.title = nextMeta;
|
||||||
|
metaEl.hidden = !nextMeta;
|
||||||
|
}
|
||||||
|
renderSteps(listEl, steps);
|
||||||
const running = steps.find((s) => s.status === 'running');
|
const running = steps.find((s) => s.status === 'running');
|
||||||
const last = steps[steps.length - 1];
|
const last = steps[steps.length - 1];
|
||||||
if (titleEl) {
|
if (titleEl) {
|
||||||
titleEl.textContent = running
|
titleEl.textContent = running
|
||||||
? running.label
|
? running.label
|
||||||
: (last?.label || 'Assistent');
|
: (last?.label || title || 'Assistent');
|
||||||
}
|
}
|
||||||
card.classList.toggle('sa-activity-live', steps.some((s) => s.status === 'running'));
|
card.classList.toggle('sa-activity-live', steps.some((s) => s.status === 'running'));
|
||||||
card.classList.toggle('sa-activity-done', steps.length > 0 && steps.every((s) => s.status === 'done' || s.status === 'skip'));
|
card.classList.toggle('sa-activity-done', steps.length > 0 && steps.every((s) => s.status === 'done' || s.status === 'skip'));
|
||||||
@@ -138,15 +334,45 @@ export function createActivityController(opts = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function begin(title) {
|
function setContext(patch = {}) {
|
||||||
|
context = { ...context, ...patch };
|
||||||
|
if (card && !metaEl) {
|
||||||
|
metaEl = renderMetaRow(context);
|
||||||
|
if (metaEl && card.firstChild) {
|
||||||
|
card.insertBefore(metaEl, card.firstChild);
|
||||||
|
} else if (metaEl) {
|
||||||
|
card.prepend(metaEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paint();
|
||||||
|
}
|
||||||
|
|
||||||
|
function begin(nextTitle, nextContext = {}) {
|
||||||
steps = [];
|
steps = [];
|
||||||
card = null;
|
card = null;
|
||||||
listEl = null;
|
listEl = null;
|
||||||
titleEl = null;
|
titleEl = null;
|
||||||
|
metaEl = null;
|
||||||
open = true;
|
open = true;
|
||||||
|
title = nextTitle || 'Assistent';
|
||||||
|
context = nextContext && typeof nextContext === 'object' ? { ...nextContext } : {};
|
||||||
ensureCard();
|
ensureCard();
|
||||||
if (titleEl && title) {
|
const meta = buildContextMeta(context);
|
||||||
titleEl.textContent = title;
|
if (meta) {
|
||||||
|
upsert('ctx', {
|
||||||
|
kind: 'ctx',
|
||||||
|
label: 'Контекст хода',
|
||||||
|
detail: meta,
|
||||||
|
status: 'done',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (context.model) {
|
||||||
|
upsert('model', {
|
||||||
|
kind: 'model',
|
||||||
|
label: 'Модель',
|
||||||
|
detail: String(context.model),
|
||||||
|
status: 'done',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
paint();
|
paint();
|
||||||
}
|
}
|
||||||
@@ -184,6 +410,9 @@ export function createActivityController(opts = {}) {
|
|||||||
s.status = 'done';
|
s.status = 'done';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (summary) {
|
||||||
|
title = summary;
|
||||||
|
}
|
||||||
if (summary && titleEl) {
|
if (summary && titleEl) {
|
||||||
titleEl.textContent = summary;
|
titleEl.textContent = summary;
|
||||||
}
|
}
|
||||||
@@ -194,6 +423,14 @@ export function createActivityController(opts = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function snapshot() {
|
||||||
|
return slimActivityTrace({
|
||||||
|
title: titleEl?.textContent || title || 'Assistent',
|
||||||
|
context,
|
||||||
|
steps: steps.slice(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** Describe a sparse model patch as human-readable activity steps. */
|
/** Describe a sparse model patch as human-readable activity steps. */
|
||||||
function noteModelCommands(patch) {
|
function noteModelCommands(patch) {
|
||||||
if (!patch || typeof patch !== 'object') {
|
if (!patch || typeof patch !== 'object') {
|
||||||
@@ -204,16 +441,16 @@ export function createActivityController(opts = {}) {
|
|||||||
if (keys.length) {
|
if (keys.length) {
|
||||||
done('delta', {
|
done('delta', {
|
||||||
kind: 'delta',
|
kind: 'delta',
|
||||||
label: 'Обновил сессию',
|
label: 'Патч сессии',
|
||||||
detail: keys.slice(0, 10).join(', '),
|
detail: keys.slice(0, 12).join(', '),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const ask = Array.isArray(patch.ask) ? patch.ask.map(String) : (patch.ask ? [String(patch.ask)] : []);
|
const ask = Array.isArray(patch.ask) ? patch.ask.map(String) : (patch.ask ? [String(patch.ask)] : []);
|
||||||
if (ask.length) {
|
if (ask.length) {
|
||||||
upsert('ask', {
|
upsert('ask', {
|
||||||
kind: 'ask',
|
kind: 'ask',
|
||||||
label: `Запросил ${ask.join(', ')}`,
|
label: `ask: ${ask.join(', ')}`,
|
||||||
detail: 'подгружаю детали…',
|
detail: 'запрос деталей у SwarmUI',
|
||||||
status: 'running',
|
status: 'running',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -221,7 +458,7 @@ export function createActivityController(opts = {}) {
|
|||||||
const slots = [].concat(patch.look_at || patch.vision_from || patch.vision_slots || []);
|
const slots = [].concat(patch.look_at || patch.vision_from || patch.vision_slots || []);
|
||||||
upsert('look', {
|
upsert('look', {
|
||||||
kind: 'look',
|
kind: 'look',
|
||||||
label: 'Смотрит на кадр',
|
label: 'look_at',
|
||||||
detail: slots.map(String).slice(0, 4).join(', '),
|
detail: slots.map(String).slice(0, 4).join(', '),
|
||||||
status: 'running',
|
status: 'running',
|
||||||
});
|
});
|
||||||
@@ -231,14 +468,14 @@ export function createActivityController(opts = {}) {
|
|||||||
upsert('generate', {
|
upsert('generate', {
|
||||||
kind: 'generate',
|
kind: 'generate',
|
||||||
label: 'Generate',
|
label: 'Generate',
|
||||||
detail: 'ждёт пайплайн…',
|
detail: 'клиент → SwarmUI',
|
||||||
status: 'running',
|
status: 'running',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (Array.isArray(patch.variants) && patch.variants.length) {
|
if (Array.isArray(patch.variants) && patch.variants.length) {
|
||||||
upsert('variants', {
|
upsert('variants', {
|
||||||
kind: 'generate',
|
kind: 'generate',
|
||||||
label: `Варианты ×${patch.variants.length}`,
|
label: `variants ×${patch.variants.length}`,
|
||||||
status: 'running',
|
status: 'running',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -246,11 +483,13 @@ export function createActivityController(opts = {}) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
begin,
|
begin,
|
||||||
|
setContext,
|
||||||
upsert,
|
upsert,
|
||||||
done,
|
done,
|
||||||
skip,
|
skip,
|
||||||
fail,
|
fail,
|
||||||
finish,
|
finish,
|
||||||
|
snapshot,
|
||||||
noteModelCommands,
|
noteModelCommands,
|
||||||
get steps() {
|
get steps() {
|
||||||
return steps.slice();
|
return steps.slice();
|
||||||
@@ -260,4 +499,6 @@ export function createActivityController(opts = {}) {
|
|||||||
|
|
||||||
export function attachActivity(SA) {
|
export function attachActivity(SA) {
|
||||||
SA.createActivityController = createActivityController;
|
SA.createActivityController = createActivityController;
|
||||||
|
SA.mountActivityTrace = mountActivityTrace;
|
||||||
|
SA.slimActivityTrace = slimActivityTrace;
|
||||||
}
|
}
|
||||||
|
|||||||
+78
-15
@@ -183,6 +183,7 @@ import { DEFAULT_ASPECT_TABLE, applyAspectTableFromObject as mergeAspectTable }
|
|||||||
ollamaHealth: 'unknown',
|
ollamaHealth: 'unknown',
|
||||||
trainingLock: false,
|
trainingLock: false,
|
||||||
activity: null,
|
activity: null,
|
||||||
|
pendingActivityTrace: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
function getActivity() {
|
function getActivity() {
|
||||||
@@ -199,13 +200,26 @@ import { DEFAULT_ASPECT_TABLE, applyAspectTableFromObject as mergeAspectTable }
|
|||||||
return state.activity;
|
return state.activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
function activityBegin(title) {
|
function activityBegin(title, context) {
|
||||||
const a = getActivity();
|
const a = getActivity();
|
||||||
if (a) {
|
if (a) {
|
||||||
a.begin(title || 'Assistent');
|
a.begin(title || 'Assistent', context || {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function captureActivityTrace() {
|
||||||
|
const a = getActivity();
|
||||||
|
if (!a || typeof a.snapshot !== 'function') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const snap = a.snapshot();
|
||||||
|
if (snap && snap.steps && snap.steps.length) {
|
||||||
|
state.pendingActivityTrace = snap;
|
||||||
|
return snap;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function activityStep(id, patch) {
|
function activityStep(id, patch) {
|
||||||
const a = getActivity();
|
const a = getActivity();
|
||||||
if (a) {
|
if (a) {
|
||||||
@@ -224,6 +238,7 @@ import { DEFAULT_ASPECT_TABLE, applyAspectTableFromObject as mergeAspectTable }
|
|||||||
const a = getActivity();
|
const a = getActivity();
|
||||||
if (a) {
|
if (a) {
|
||||||
a.finish(summary);
|
a.finish(summary);
|
||||||
|
captureActivityTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3255,6 +3270,9 @@ import { DEFAULT_ASPECT_TABLE, applyAspectTableFromObject as mergeAspectTable }
|
|||||||
content: content.slice(0, 4000),
|
content: content.slice(0, 4000),
|
||||||
persona: m.persona || undefined,
|
persona: m.persona || undefined,
|
||||||
pack: m.pack || undefined,
|
pack: m.pack || undefined,
|
||||||
|
activity: (window.SA && typeof SA.slimActivityTrace === 'function')
|
||||||
|
? SA.slimActivityTrace(m.activity)
|
||||||
|
: (m.activity || undefined),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -3644,6 +3662,7 @@ import { DEFAULT_ASPECT_TABLE, applyAspectTableFromObject as mergeAspectTable }
|
|||||||
persona: m.persona ? { id: m.persona, title: m.persona } : null,
|
persona: m.persona ? { id: m.persona, title: m.persona } : null,
|
||||||
pack: m.pack,
|
pack: m.pack,
|
||||||
historical: true,
|
historical: true,
|
||||||
|
activity: m.activity,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5216,7 +5235,14 @@ import { DEFAULT_ASPECT_TABLE, applyAspectTableFromObject as mergeAspectTable }
|
|||||||
} else {
|
} else {
|
||||||
appendMessage('assistant', prose);
|
appendMessage('assistant', prose);
|
||||||
}
|
}
|
||||||
state.history.push({ role: 'assistant', content: prose, persona, pack });
|
state.history.push({
|
||||||
|
role: 'assistant',
|
||||||
|
content: prose,
|
||||||
|
persona,
|
||||||
|
pack,
|
||||||
|
activity: captureActivityTrace() || state.pendingActivityTrace || undefined,
|
||||||
|
});
|
||||||
|
state.pendingActivityTrace = null;
|
||||||
persistHistory();
|
persistHistory();
|
||||||
state.turnSettled = true;
|
state.turnSettled = true;
|
||||||
}
|
}
|
||||||
@@ -5820,6 +5846,10 @@ import { DEFAULT_ASPECT_TABLE, applyAspectTableFromObject as mergeAspectTable }
|
|||||||
if (role === 'assistant' && !(meta && meta.historical)) {
|
if (role === 'assistant' && !(meta && meta.historical)) {
|
||||||
mountCurateButtons(div, meta);
|
mountCurateButtons(div, meta);
|
||||||
}
|
}
|
||||||
|
if (role === 'assistant' && meta?.historical && meta?.activity
|
||||||
|
&& window.SA && typeof SA.mountActivityTrace === 'function') {
|
||||||
|
SA.mountActivityTrace(meta.activity, { box, insertBefore: div, collapsed: true });
|
||||||
|
}
|
||||||
box.appendChild(div);
|
box.appendChild(div);
|
||||||
scrollMessagesToBottom({ force: true });
|
scrollMessagesToBottom({ force: true });
|
||||||
return div;
|
return div;
|
||||||
@@ -8978,16 +9008,6 @@ import { DEFAULT_ASPECT_TABLE, applyAspectTableFromObject as mergeAspectTable }
|
|||||||
// If Krea just ran, expectColdLoad stays true until warm / first token — don't clear it here.
|
// If Krea just ran, expectColdLoad stays true until warm / first token — don't clear it here.
|
||||||
state.llmParked = false;
|
state.llmParked = false;
|
||||||
setInterruptVisible(true);
|
setInterruptVisible(true);
|
||||||
if (!isContinuationTurn(opts) && !opts.fromAskHop) {
|
|
||||||
activityBegin(opts.fromDebug ? 'Debug' : 'Ход Assistent');
|
|
||||||
activityStep('think', { kind: 'think', label: 'Думаю…', status: 'running' });
|
|
||||||
} else if (opts.fromAskHop) {
|
|
||||||
activityStep('think', { kind: 'think', label: 'Отвечает с деталями…', status: 'running' });
|
|
||||||
} else if (opts.fromVisionHop) {
|
|
||||||
activityStep('look', { kind: 'look', label: 'Смотрит изображение…', status: 'running' });
|
|
||||||
} else if (opts.fromPromptEnRetry) {
|
|
||||||
activityStep('prep', { kind: 'prep', label: 'Дописываю EN-промпт…', status: 'running' });
|
|
||||||
}
|
|
||||||
if (state.expectColdLoad && !isContinuationTurn(opts)) {
|
if (state.expectColdLoad && !isContinuationTurn(opts)) {
|
||||||
startBusyUi('warming');
|
startBusyUi('warming');
|
||||||
setStatus('Возвращаю LLM в GPU…');
|
setStatus('Возвращаю LLM в GPU…');
|
||||||
@@ -9073,6 +9093,31 @@ import { DEFAULT_ASPECT_TABLE, applyAspectTableFromObject as mergeAspectTable }
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isContinuationTurn(opts) && !opts.fromAskHop) {
|
||||||
|
state.pendingActivityTrace = null;
|
||||||
|
const hopLabel = opts.fromVisionHop ? 'vision hop'
|
||||||
|
: (opts.fromPromptEnRetry ? 'krea prep'
|
||||||
|
: (opts.fromAutoCritique ? 'critique'
|
||||||
|
: (opts.fromDebug ? 'debug' : '')));
|
||||||
|
activityBegin(opts.fromDebug ? 'Debug' : 'Ход Assistent', {
|
||||||
|
model,
|
||||||
|
pack,
|
||||||
|
persona,
|
||||||
|
skills: opts.fromDebug ? [] : (state.enabledSkills || []),
|
||||||
|
hop: hopLabel || undefined,
|
||||||
|
});
|
||||||
|
activityStep('think', { kind: 'think', label: 'Думаю…', status: 'running' });
|
||||||
|
} else if (opts.fromAskHop) {
|
||||||
|
getActivity()?.setContext?.({ hop: 'ask hop', pack, model });
|
||||||
|
activityStep('think', { kind: 'think', label: 'Отвечает с деталями…', status: 'running' });
|
||||||
|
} else if (opts.fromVisionHop) {
|
||||||
|
getActivity()?.setContext?.({ hop: 'vision hop' });
|
||||||
|
activityStep('look', { kind: 'look', label: 'Смотрит изображение…', status: 'running' });
|
||||||
|
} else if (opts.fromPromptEnRetry) {
|
||||||
|
getActivity()?.setContext?.({ hop: 'krea prep' });
|
||||||
|
activityStep('prep', { kind: 'prep', label: 'Дописываю EN-промпт…', status: 'running' });
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-compress after the new user turn is in history (budget includes it).
|
// Auto-compress after the new user turn is in history (budget includes it).
|
||||||
if (!opts.fromDebug && !opts.fromCompress && !opts.fromAskHop && !opts.fromVisionHop
|
if (!opts.fromDebug && !opts.fromCompress && !opts.fromAskHop && !opts.fromVisionHop
|
||||||
&& !opts.fromAutoCritique && !isContinuationTurn(opts)) {
|
&& !opts.fromAutoCritique && !isContinuationTurn(opts)) {
|
||||||
@@ -9157,8 +9202,6 @@ import { DEFAULT_ASPECT_TABLE, applyAspectTableFromObject as mergeAspectTable }
|
|||||||
}
|
}
|
||||||
updateCtxChip();
|
updateCtxChip();
|
||||||
const prose = visibleAssistantProse(reply);
|
const prose = visibleAssistantProse(reply);
|
||||||
state.history.push({ role: 'assistant', content: prose, persona, pack });
|
|
||||||
persistHistory();
|
|
||||||
setBusyPhase(state.pendingSilentGen ? 'silent_gen' : 'thinking');
|
setBusyPhase(state.pendingSilentGen ? 'silent_gen' : 'thinking');
|
||||||
try {
|
try {
|
||||||
await handleReplySideEffects(reply, civitaiResults, {
|
await handleReplySideEffects(reply, civitaiResults, {
|
||||||
@@ -9169,6 +9212,26 @@ import { DEFAULT_ASPECT_TABLE, applyAspectTableFromObject as mergeAspectTable }
|
|||||||
|| (!isMachineTurn(opts) && state.pendingSilentGen),
|
|| (!isMachineTurn(opts) && state.pendingSilentGen),
|
||||||
attachedSlotIds: visionSlots.map((s) => s.id),
|
attachedSlotIds: visionSlots.map((s) => s.id),
|
||||||
});
|
});
|
||||||
|
if (meta.system_layers && typeof meta.system_layers === 'object') {
|
||||||
|
const layers = Object.keys(meta.system_layers).filter((k) => meta.system_layers[k]);
|
||||||
|
if (layers.length) {
|
||||||
|
activityDone('layers', {
|
||||||
|
kind: 'ctx',
|
||||||
|
label: 'system_layers',
|
||||||
|
detail: layers.slice(0, 12).join(', '),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
captureActivityTrace();
|
||||||
|
state.history.push({
|
||||||
|
role: 'assistant',
|
||||||
|
content: prose,
|
||||||
|
persona,
|
||||||
|
pack,
|
||||||
|
activity: state.pendingActivityTrace || undefined,
|
||||||
|
});
|
||||||
|
state.pendingActivityTrace = null;
|
||||||
|
persistHistory();
|
||||||
} finally {
|
} finally {
|
||||||
if (chatEpoch !== state.chatEpoch) {
|
if (chatEpoch !== state.chatEpoch) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { describe, it } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { slimActivityTrace } from '../src/activity.js';
|
||||||
|
|
||||||
|
describe('activity.js', () => {
|
||||||
|
it('slimActivityTrace keeps model/pack steps for history', () => {
|
||||||
|
const slim = slimActivityTrace({
|
||||||
|
title: 'Готово',
|
||||||
|
context: { model: 'qwen3:8b', pack: 'ordinary', persona: 'neutral' },
|
||||||
|
steps: [
|
||||||
|
{ id: 'model', kind: 'model', label: 'Модель', detail: 'qwen3:8b', status: 'done' },
|
||||||
|
{ id: 'generate', kind: 'generate', label: 'Generate', detail: 'ok', status: 'done' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
assert.ok(slim);
|
||||||
|
assert.equal(slim.context.model, 'qwen3:8b');
|
||||||
|
assert.equal(slim.steps.length, 2);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user