Files
youssefvdel 5de858abba feat: dashboard refactor — claymorphism style, unified sidebar, network page fixes
Dashboard UI (Astro+SolidJS → vanilla HTML/JS):
- 5 standalone page files at src/routes/dashboard/
  (overview, logs, accounts, network, settings)
- Claymorphism style with warm cream/beige palette
  (#F5F1EA bg, #5E9D5C accent, vibrant greens)
- Unified sidebar navigation across all pages
- 12-hour time format, 100% width layout
- Bold/display text uses grayish --text-display (#4a4540)

Overview page:
- Grid layout: KPIs (2fr) + System Logs (1fr) side by side
- Responsive collapse at 1100px

Logs page:
- Two-column entry layout (70/30 split)
- Raw Output + Processed Output side by side
- Chunk Stream fills 100% height with internal scroll
- Input folded by default, other sections unfolded

Network page — bug fixes:
- Fix template literal escape bug: \' → "'" to avoid
  JS syntax error (empty string concat broke onclick handler)
- Fix \n → \n in join() calls to prevent actual newline
  injection into generated JS
- All escape sequences properly double-escaped in template

Other:
- New src/index.tsx (replaces src/index.ts)
- Session pool, qwen service, auth, tools refactoring
- StreamingContentFilter, logStore, networkDebug updates
- Updated scripts (setup, loadtest, qg CLI)
- 141/141 tests pass
2026-06-03 07:51:56 +03:00

58 lines
2.1 KiB
TypeScript

const BASE_URL = process.env.BASE_URL || 'http://localhost:26405';
const CONCURRENCY = parseInt(process.env.CONCURRENCY || '10', 10);
const REQUESTS = parseInt(process.env.REQUESTS || '100', 10);
const ENDPOINT = process.env.ENDPOINT || '/health';
let errors = 0;
let latencies: number[] = [];
async function makeRequest(_id: number): Promise<void> {
const start = Date.now();
try {
const res = await fetch(`${BASE_URL}${ENDPOINT}`, {
method: ENDPOINT === '/v1/chat/completions' ? 'POST' : 'GET',
headers: { 'Content-Type': 'application/json' },
body: ENDPOINT === '/v1/chat/completions' ? JSON.stringify({
model: 'qwen3.7-max',
messages: [{ role: 'user', content: 'load test' }],
stream: false
}) : undefined
});
const latency = Date.now() - start;
latencies.push(latency);
if (!res.ok) errors++;
} catch (_e) {
errors++;
}
}
async function runLoadTest(): Promise<void> {
console.log(`Starting load test: ${REQUESTS} requests, ${CONCURRENCY} concurrent, endpoint: ${ENDPOINT}`);
const startTime = Date.now();
for (let i = 0; i < REQUESTS; i += CONCURRENCY) {
const batch = [];
for (let j = 0; j < CONCURRENCY && i + j < REQUESTS; j++) {
batch.push(makeRequest(i + j));
}
await Promise.all(batch);
}
const totalTime = Date.now() - startTime;
const avgLatency = latencies.reduce((a, b) => a + b, 0) / latencies.length;
const p95 = latencies.sort((a, b) => a - b)[Math.floor(latencies.length * 0.95)];
const p99 = latencies.sort((a, b) => a - b)[Math.floor(latencies.length * 0.99)];
console.log('\n=== Load Test Results ===');
console.log(`Total requests: ${REQUESTS}`);
console.log(`Successful: ${REQUESTS - errors}`);
console.log(`Errors: ${errors}`);
console.log(`Total time: ${totalTime}ms`);
console.log(`Throughput: ${(REQUESTS / totalTime * 1000).toFixed(2)} req/s`);
console.log(`Avg latency: ${avgLatency.toFixed(2)}ms`);
console.log(`P95 latency: ${p95?.toFixed(2) || 'N/A'}ms`);
console.log(`P99 latency: ${p99?.toFixed(2) || 'N/A'}ms`);
}
runLoadTest().catch(console.error);