mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
perf(client): reduce redundant summary and polling API calls (#2565)
* perf(client): reduce redundant summary and polling API calls - summary cache capacity 32→128 to eliminate eviction-driven re-fetches - IntelligenceGapBadge: skip polling when tab not visible - McpConnectModal: raise min refresh interval 10→60s * fix(tests): replace eval(), harden regexes, extract MCP refresh constant - Replace eval() in summary-cache-capacity test with regex assertion - Scope persistCache test to summaryResultBreaker block - Harden intelligence-gap-badge regex: [^}]* → [\s\S]*? (nested braces) - Extract MIN_MCP_REFRESH_S constant in McpConnectModal (DRY) - Update mcp-connect-modal test to verify constant usage --------- Co-authored-by: Elie Habib <elie.habib@gmail.com>
This commit is contained in:
@@ -229,7 +229,9 @@ export class IntelligenceFindingsBadge {
|
||||
|
||||
private startRefresh(): void {
|
||||
document.addEventListener('wm:intelligence-updated', this.boundUpdate);
|
||||
this.refreshInterval = setInterval(this.boundUpdate, REFRESH_INTERVAL_MS);
|
||||
this.refreshInterval = setInterval(() => {
|
||||
if (document.visibilityState === 'visible') this.boundUpdate();
|
||||
}, REFRESH_INTERVAL_MS);
|
||||
}
|
||||
|
||||
public update(): void {
|
||||
|
||||
@@ -10,6 +10,8 @@ interface McpConnectOptions {
|
||||
onComplete: (spec: McpPanelSpec) => void;
|
||||
}
|
||||
|
||||
const MIN_MCP_REFRESH_S = 60;
|
||||
|
||||
let overlay: HTMLElement | null = null;
|
||||
|
||||
/** Build a header Record from a template + key value.
|
||||
@@ -141,8 +143,8 @@ export function openMcpConnectModal(options: McpConnectOptions): void {
|
||||
</div>
|
||||
<div class="mcp-form-group mcp-refresh-group">
|
||||
<label class="mcp-label">${escapeHtml(t('mcp.refreshEvery'))}</label>
|
||||
<input class="mcp-input mcp-refresh-input" type="number" min="10" max="86400"
|
||||
value="${existing ? Math.round(existing.refreshIntervalMs / 1000) : 60}" />
|
||||
<input class="mcp-input mcp-refresh-input" type="number" min="${MIN_MCP_REFRESH_S}" max="86400"
|
||||
value="${existing ? Math.round(existing.refreshIntervalMs / 1000) : MIN_MCP_REFRESH_S}" />
|
||||
<span class="mcp-refresh-unit">${escapeHtml(t('mcp.seconds'))}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -428,7 +430,7 @@ export function openMcpConnectModal(options: McpConnectOptions): void {
|
||||
customHeaders: getEffectiveHeaders(),
|
||||
toolName: selectedTool.name,
|
||||
toolArgs,
|
||||
refreshIntervalMs: Math.max(10, parseInt(refreshInput.value, 10) || 60) * 1000,
|
||||
refreshIntervalMs: Math.max(MIN_MCP_REFRESH_S, parseInt(refreshInput.value, 10) || MIN_MCP_REFRESH_S) * 1000,
|
||||
createdAt: existing?.createdAt ?? Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ const summaryResultBreaker = createCircuitBreaker<SummarizationResult | null>({
|
||||
name: 'SummaryResult',
|
||||
cacheTtlMs: 2 * 60 * 60 * 1000,
|
||||
persistCache: true,
|
||||
maxCacheEntries: 32,
|
||||
maxCacheEntries: 128,
|
||||
});
|
||||
|
||||
const emptySummaryFallback: SummarizeArticleResponse = { summary: '', provider: '', model: '', fallback: true, tokens: 0, error: '', errorType: '', status: 'SUMMARIZE_STATUS_UNSPECIFIED', statusDetail: '' };
|
||||
|
||||
18
tests/intelligence-gap-badge-polling.test.mts
Normal file
18
tests/intelligence-gap-badge-polling.test.mts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const src = readFileSync(resolve(__dirname, '..', 'src', 'components', 'IntelligenceGapBadge.ts'), 'utf-8');
|
||||
|
||||
describe('IntelligenceGapBadge polling', () => {
|
||||
it('setInterval callback includes visibilityState check', () => {
|
||||
assert.match(
|
||||
src,
|
||||
/setInterval\(\s*\(\)\s*=>\s*\{[\s\S]*?visibilityState/s,
|
||||
'setInterval callback must check document.visibilityState to avoid background-tab polling',
|
||||
);
|
||||
});
|
||||
});
|
||||
24
tests/mcp-connect-modal-interval.test.mts
Normal file
24
tests/mcp-connect-modal-interval.test.mts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const src = readFileSync(resolve(__dirname, '..', 'src', 'components', 'McpConnectModal.ts'), 'utf-8');
|
||||
|
||||
describe('McpConnectModal refresh interval', () => {
|
||||
it('MIN_MCP_REFRESH_S constant is at least 60', () => {
|
||||
const m = src.match(/const\s+MIN_MCP_REFRESH_S\s*=\s*(\d+)/);
|
||||
assert.ok(m, 'MIN_MCP_REFRESH_S constant not found');
|
||||
assert.ok(Number(m![1]) >= 60, `MIN_MCP_REFRESH_S is ${m![1]}, expected >= 60`);
|
||||
});
|
||||
|
||||
it('Math.max uses MIN_MCP_REFRESH_S constant', () => {
|
||||
assert.match(src, /Math\.max\(MIN_MCP_REFRESH_S,\s*parseInt\(refreshInput/, 'Math.max should use MIN_MCP_REFRESH_S');
|
||||
});
|
||||
|
||||
it('HTML input min uses MIN_MCP_REFRESH_S constant', () => {
|
||||
assert.match(src, /min="\$\{MIN_MCP_REFRESH_S\}"/, 'HTML min should use MIN_MCP_REFRESH_S');
|
||||
});
|
||||
});
|
||||
26
tests/summary-cache-capacity.test.mts
Normal file
26
tests/summary-cache-capacity.test.mts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const src = readFileSync(resolve(__dirname, '..', 'src', 'services', 'summarization.ts'), 'utf-8');
|
||||
|
||||
describe('summary circuit breaker configuration', () => {
|
||||
it('maxCacheEntries is at least 128', () => {
|
||||
const m = src.match(/maxCacheEntries:\s*(\d+)/);
|
||||
assert.ok(m, 'maxCacheEntries not found in summarization.ts');
|
||||
assert.ok(Number(m![1]) >= 128, `maxCacheEntries is ${m![1]}, expected >= 128`);
|
||||
});
|
||||
|
||||
it('persistCache is enabled on summaryResultBreaker', () => {
|
||||
assert.match(src, /summaryResultBreaker[\s\S]*?persistCache:\s*true/, 'persistCache should be true on summaryResultBreaker');
|
||||
});
|
||||
|
||||
it('cacheTtlMs is 2 hours on summaryResultBreaker', () => {
|
||||
const block = src.match(/summaryResultBreaker[\s\S]*?cacheTtlMs:\s*([\d\s*]+)/);
|
||||
assert.ok(block, 'cacheTtlMs not found in summaryResultBreaker block');
|
||||
assert.match(block![1].trim(), /^2\s*\*\s*60\s*\*\s*60\s*\*\s*1000$/, `cacheTtlMs should be 2 * 60 * 60 * 1000, got "${block![1].trim()}"`);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user