spb/zyquo-cloud-web Public MIT
Zyquo Cloud Web — every cloud model, one beautiful chat, entirely in your browser.
TypeScript 81.9%
CSS 8.9%
JavaScript 7.5%
Shell 1.1%
HTML 0.6%
1/*2 * gate-phase2.mjs3 * Zyquo Cloud Web4 *5 * Author: Simon-Pierre Boucher6 * Mail: contact@spboucher.ai7 *8 * Phase 2 gate check: drives a real Chromium browser against the dev server,9 * seeds a provider key from .keys.local.json into localStorage, runs the bare10 * stream test view, and asserts the completion arrived TOKEN-BY-TOKEN11 * (multiple incremental output snapshots) with working Stop cancellation.12 * Usage: node scripts/gate-phase2.mjs [provider] [baseURL]13 */1415import { chromium } from 'playwright'16import { readFileSync } from 'node:fs'17import { fileURLToPath } from 'node:url'18import { dirname, join } from 'node:path'1920const root = dirname(dirname(fileURLToPath(import.meta.url)))21const keys = JSON.parse(readFileSync(join(root, '.keys.local.json'), 'utf8'))22const provider = process.argv[2] ?? 'openai'23const baseURL = process.argv[3] ?? 'http://localhost:5173'2425const browser = await chromium.launch()26const page = await browser.newPage()2728await page.goto(baseURL)29await page.evaluate(30 ([p, k]) => localStorage.setItem('zyquo.cloud.web.keys', JSON.stringify({ [p]: k })),31 [provider, keys[provider]]32)33await page.goto(`${baseURL}/#stream-test`)34await page.reload()3536await page.selectOption('[data-testid="provider"]', provider)37await page.fill('[data-testid="prompt"]', 'Write a 150-word paragraph about clouds.')38await page.click('[data-testid="run"]')3940// Sample the output as it streams to prove token-by-token rendering.41const snapshots = []42for (let i = 0; i < 600; i++) {43 const status = (await page.textContent('[data-testid="status"]'))?.replace('status: ', '') ?? ''44 const output = await page.textContent('[data-testid="output"]')45 if (output && (snapshots.length === 0 || output !== snapshots[snapshots.length - 1])) {46 snapshots.push(output)47 }48 if (49 status.startsWith('finished') ||50 /invalidAPIKey|badRequest|serverError|networkError|rateLimited|invalidResponse/.test(status)51 ) {52 break53 }54 await page.waitForTimeout(50)55}5657const status = await page.textContent('[data-testid="status"]')58const tokens = await page.textContent('[data-testid="tokens"]')59const finalOutput = snapshots[snapshots.length - 1] ?? ''6061console.log(`provider: ${provider}`)62console.log(`status: ${status}`)63console.log(`usage: ${tokens}`)64console.log(`snapshots: ${snapshots.length} incremental output states`)65console.log(`final out: ${finalOutput.slice(0, 120).replace(/\n/g, ' / ')}`)6667// Cancellation check: start a second stream and stop it immediately.68await page.click('[data-testid="run"]')69await page.waitForTimeout(700)70await page.click('[data-testid="stop"]')71await page.waitForTimeout(700)72const stopStatus = await page.textContent('[data-testid="status"]')73console.log(`stop test: ${stopStatus}`)7475await browser.close()7677const streamedIncrementally = snapshots.length >= 378const finished = status?.includes('finished')79const stopped = stopStatus?.includes('cancelled') || stopStatus?.includes('stopped')80if (streamedIncrementally && finished && stopped) {81 console.log('GATE PASS: real token-by-token streaming + cancellation in a real browser')82} else {83 console.error('GATE FAIL')84 process.exit(1)85}86