SPB Git

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%
4.6 KB · 146 lines tsx
Raw Blame History
1/*2 *  StreamTestView.tsx3 *  Zyquo Cloud Web4 *5 *  Author: Simon-Pierre Boucher6 *  Mail: contact@spboucher.ai7 *8 *  Phase 2 gate: a bare test view that renders a REAL streaming completion9 *  token-by-token against any configured provider, using a key from10 *  localStorage. Reached via #stream-test. Superseded by the real chat UI in11 *  Phase 4 but kept for verification (Phase 7 drives it programmatically).12 */1314import { useRef, useState } from 'react'15import { cheapestModel, findModel, modelsFor } from '../providers/catalog'16import { clientFor } from '../providers/registry'17import { ProviderError } from '../providers/types'18import { getKey, configuredProviders } from '../storage/keys'19import { BUILT_IN_PROVIDERS, type Provider } from '../types'2021export default function StreamTestView() {22  const [provider, setProvider] = useState<Provider>('openai')23  const [modelID, setModelID] = useState('')24  const [prompt, setPrompt] = useState('Count from 1 to 10, one number per line.')25  const [output, setOutput] = useState('')26  const [reasoning, setReasoning] = useState('')27  const [status, setStatus] = useState('idle')28  const [tokens, setTokens] = useState('')29  const abortRef = useRef<AbortController | null>(null)3031  const run = async () => {32    const key = getKey(provider)33    if (!key) {34      setStatus(`no key for ${provider} in localStorage`)35      return36    }37    const model =38      (modelID !== '' ? findModel(provider, modelID) : undefined) ??39      cheapestModel(provider) ??40      modelsFor(provider)[0]41    if (!model) {42      setStatus('no model')43      return44    }45    setOutput('')46    setReasoning('')47    setTokens('')48    setStatus(`streaming ${model.id}…`)49    const controller = new AbortController()50    abortRef.current = controller51    try {52      const client = clientFor(model)53      const events = client.streamChat(54        {55          model,56          messages: [57            {58              id: 't1',59              role: 'user',60              text: prompt,61              createdAt: Date.now(),62            },63          ],64          parameters: { maxTokens: 256 },65          stream: true,66        },67        key,68        controller.signal69      )70      for await (const event of events) {71        if (event.type === 'textDelta') setOutput((prev) => prev + event.text)72        else if (event.type === 'reasoningDelta') setReasoning((prev) => prev + event.text)73        else if (event.type === 'usage')74          setTokens(`${event.usage.inputTokens}→${event.usage.outputTokens} tok`)75        else if (event.type === 'finished') setStatus(`finished (${event.reason ?? 'n/a'})`)76      }77    } catch (err) {78      setStatus(err instanceof ProviderError ? `${err.kind}: ${err.message}` : String(err))79    } finally {80      abortRef.current = null81    }82  }8384  return (85    <div86      style={{87        padding: 'var(--z-space-xl)',88        maxWidth: 720,89        margin: '0 auto',90        height: '100%',91        overflow: 'auto',92        fontFamily: 'var(--z-font-mono)',93        fontSize: 12.5,94      }}95    >96      <h2 style={{ fontFamily: 'var(--z-font-sans)' }}>Zyquo Cloud Web — stream test</h2>97      <p data-testid="configured">configured: {configuredProviders().join(', ') || '(none)'}</p>98      <div style={{ display: 'flex', gap: 8, marginBottom: 12, flexWrap: 'wrap' }}>99        <select100          data-testid="provider"101          value={provider}102          onChange={(e) => setProvider(e.target.value as Provider)}103        >104          {BUILT_IN_PROVIDERS.map((p) => (105            <option key={p} value={p}>106              {p}107            </option>108          ))}109        </select>110        <input111          data-testid="model"112          placeholder="model id (blank = cheapest)"113          value={modelID}114          onChange={(e) => setModelID(e.target.value)}115          style={{ width: 260 }}116        />117        <input118          data-testid="prompt"119          value={prompt}120          onChange={(e) => setPrompt(e.target.value)}121          style={{ width: 320 }}122        />123        <button data-testid="run" onClick={() => void run()}>124          Stream125        </button>126        <button data-testid="stop" onClick={() => abortRef.current?.abort()}>127          Stop128        </button>129      </div>130      <p data-testid="status">status: {status}</p>131      <p data-testid="tokens">{tokens}</p>132      {reasoning !== '' && (133        <pre134          data-testid="reasoning"135          style={{ whiteSpace: 'pre-wrap', color: 'var(--z-text-tertiary)' }}136        >137          {reasoning}138        </pre>139      )}140      <pre data-testid="output" style={{ whiteSpace: 'pre-wrap' }}>141        {output}142      </pre>143    </div>144  )145}146