mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-16 19:51:11 +00:00
fix(qa-lab): skip render when poll data unchanged and use dropdown model selectors
This commit is contained in:
@@ -85,6 +85,40 @@ export async function createQaLabApp(root: HTMLDivElement) {
|
||||
let chatScrollLocked = true;
|
||||
let previousMessageCount = 0;
|
||||
|
||||
/* ---------- Render guards (avoid DOM churn during polling) ---------- */
|
||||
|
||||
let lastFingerprint = "";
|
||||
let renderDeferred = false;
|
||||
|
||||
function stateFingerprint(): string {
|
||||
const msgs = state.snapshot?.messages;
|
||||
const ev = state.snapshot?.events;
|
||||
return JSON.stringify({
|
||||
mc: msgs?.length ?? 0,
|
||||
lm: msgs && msgs.length > 0 ? msgs[msgs.length - 1].id : null,
|
||||
cc: state.snapshot?.conversations.length ?? 0,
|
||||
tc: state.snapshot?.threads.length ?? 0,
|
||||
ec: ev?.length ?? 0,
|
||||
lc: ev && ev.length > 0 ? ev[ev.length - 1].cursor : -1,
|
||||
rs: state.bootstrap?.runner.status,
|
||||
ra: state.bootstrap?.runner.startedAt,
|
||||
rf: state.bootstrap?.runner.finishedAt,
|
||||
re: state.bootstrap?.runner.error,
|
||||
ss: state.scenarioRun?.status,
|
||||
sc: state.scenarioRun?.counts,
|
||||
so: state.scenarioRun?.scenarios.map((o) => o.status).join(","),
|
||||
rp: state.latestReport?.generatedAt,
|
||||
cs: state.bootstrap?.runnerCatalog.status,
|
||||
cl: state.bootstrap?.runnerCatalog.real.length ?? 0,
|
||||
er: state.error,
|
||||
});
|
||||
}
|
||||
|
||||
function isSelectOpen(): boolean {
|
||||
const active = document.activeElement;
|
||||
return !!active && root.contains(active) && active.tagName === "SELECT";
|
||||
}
|
||||
|
||||
/* ---------- Data fetching ---------- */
|
||||
|
||||
async function refresh() {
|
||||
@@ -125,7 +159,17 @@ export async function createQaLabApp(root: HTMLDivElement) {
|
||||
} catch (error) {
|
||||
state.error = error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
render();
|
||||
|
||||
/* Only re-render when data actually changed; defer if a <select> is open */
|
||||
const fp = stateFingerprint();
|
||||
if (fp !== lastFingerprint) {
|
||||
lastFingerprint = fp;
|
||||
renderDeferred = true;
|
||||
}
|
||||
if (renderDeferred && !isSelectOpen()) {
|
||||
renderDeferred = false;
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Draft mutations ---------- */
|
||||
@@ -453,16 +497,16 @@ export async function createQaLabApp(root: HTMLDivElement) {
|
||||
root.querySelector<HTMLInputElement>("#fast-mode")?.addEventListener("change", (e) => {
|
||||
updateRunnerDraft((d) => ({ ...d, fastMode: (e.currentTarget as HTMLInputElement).checked }));
|
||||
});
|
||||
root.querySelector<HTMLElement>("#primary-model")?.addEventListener("input", (e) => {
|
||||
root.querySelector<HTMLSelectElement>("#primary-model")?.addEventListener("change", (e) => {
|
||||
updateRunnerDraft((d) => ({
|
||||
...d,
|
||||
primaryModel: (e.currentTarget as HTMLInputElement).value,
|
||||
primaryModel: (e.currentTarget as HTMLSelectElement).value,
|
||||
}));
|
||||
});
|
||||
root.querySelector<HTMLElement>("#alternate-model")?.addEventListener("input", (e) => {
|
||||
root.querySelector<HTMLSelectElement>("#alternate-model")?.addEventListener("change", (e) => {
|
||||
updateRunnerDraft((d) => ({
|
||||
...d,
|
||||
alternateModel: (e.currentTarget as HTMLInputElement).value,
|
||||
alternateModel: (e.currentTarget as HTMLSelectElement).value,
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@@ -209,6 +209,23 @@ const AVATAR_COLORS = [
|
||||
"#e879f9",
|
||||
];
|
||||
|
||||
const MOCK_MODELS: RunnerModelOption[] = [
|
||||
{
|
||||
key: "mock-openai/gpt-5.4",
|
||||
name: "GPT-5.4 (mock)",
|
||||
provider: "mock-openai",
|
||||
input: "text",
|
||||
preferred: true,
|
||||
},
|
||||
{
|
||||
key: "mock-openai/gpt-5.4-alt",
|
||||
name: "GPT-5.4 Alt (mock)",
|
||||
provider: "mock-openai",
|
||||
input: "text",
|
||||
preferred: false,
|
||||
},
|
||||
];
|
||||
|
||||
function avatarColor(name: string): string {
|
||||
let h = 0;
|
||||
for (const ch of name) {
|
||||
@@ -330,7 +347,8 @@ function renderSidebar(state: UiState): string {
|
||||
const run = state.scenarioRun;
|
||||
const isRunning = runner?.status === "running";
|
||||
const realModels = state.bootstrap?.runnerCatalog.real ?? [];
|
||||
const usesRealCatalog = selection?.providerMode === "live-openai" && realModels.length > 0;
|
||||
const modelOptions =
|
||||
selection?.providerMode === "live-openai" && realModels.length > 0 ? realModels : MOCK_MODELS;
|
||||
const selectedIds = new Set(selection?.scenarioIds ?? []);
|
||||
|
||||
return `
|
||||
@@ -345,34 +363,20 @@ function renderSidebar(state: UiState): string {
|
||||
<option value="live-openai"${selection?.providerMode === "live-openai" ? " selected" : ""}>Real providers</option>
|
||||
</select>
|
||||
</div>
|
||||
${
|
||||
usesRealCatalog
|
||||
? renderModelSelect({
|
||||
id: "primary-model",
|
||||
label: "Primary model",
|
||||
value: selection?.primaryModel ?? "",
|
||||
options: realModels,
|
||||
disabled: isRunning,
|
||||
})
|
||||
: `<div class="config-field">
|
||||
<span class="config-label">Primary model</span>
|
||||
<input id="primary-model" value="${esc(selection?.primaryModel ?? "")}"${isRunning ? " disabled" : ""} />
|
||||
</div>`
|
||||
}
|
||||
${
|
||||
usesRealCatalog
|
||||
? renderModelSelect({
|
||||
id: "alternate-model",
|
||||
label: "Alternate model",
|
||||
value: selection?.alternateModel ?? "",
|
||||
options: realModels,
|
||||
disabled: isRunning,
|
||||
})
|
||||
: `<div class="config-field">
|
||||
<span class="config-label">Alternate model</span>
|
||||
<input id="alternate-model" value="${esc(selection?.alternateModel ?? "")}"${isRunning ? " disabled" : ""} />
|
||||
</div>`
|
||||
}
|
||||
${renderModelSelect({
|
||||
id: "primary-model",
|
||||
label: "Primary model",
|
||||
value: selection?.primaryModel ?? "",
|
||||
options: modelOptions,
|
||||
disabled: isRunning,
|
||||
})}
|
||||
${renderModelSelect({
|
||||
id: "alternate-model",
|
||||
label: "Alternate model",
|
||||
value: selection?.alternateModel ?? "",
|
||||
options: modelOptions,
|
||||
disabled: isRunning,
|
||||
})}
|
||||
<div class="config-row">
|
||||
<label><input id="fast-mode" type="checkbox"${selection?.fastMode ? " checked" : ""}${isRunning ? " disabled" : ""} /> Fast mode</label>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user