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 * index.ts (types)3 * Zyquo Cloud Web4 *5 * Author: Simon-Pierre Boucher6 * Mail: contact@spboucher.ai7 *8 * Core data model, ported 1:1 from native Zyquo Cloud9 * (ProviderID.swift, AIModel.swift, Message.swift, Conversation.swift).10 */1112/** The 12 built-in cloud AI providers, plus user-defined custom endpoints. */13export type Provider =14 | 'openai'15 | 'anthropic'16 | 'xai'17 | 'mistral'18 | 'gemini'19 | 'qwen'20 | 'deepseek'21 | 'kimi'22 | 'perplexity'23 | 'together'24 | 'deepinfra'25 | 'cerebras'26 | 'custom'2728export const BUILT_IN_PROVIDERS: readonly Provider[] = [29 'openai',30 'anthropic',31 'xai',32 'mistral',33 'gemini',34 'qwen',35 'deepseek',36 'kimi',37 'perplexity',38 'together',39 'deepinfra',40 'cerebras',41]4243/** The request/response schema a provider speaks. */44export type WireFormat = 'openAIChatCompletions' | 'anthropicMessages'4546/** What a model can do. Drives UI affordances and request construction. */47export interface ModelCapabilities {48 vision: boolean49 tools: boolean50 reasoning: boolean51 streaming: boolean52 jsonMode: boolean53 citations: boolean54}5556/** USD per 1M tokens (base rate — UI labels costs as estimates). */57export interface ModelPricing {58 inputPerMTok: number59 outputPerMTok: number60}6162/** Estimated cost in USD for a usage record. */63export function pricingCost(pricing: ModelPricing, inputTokens: number, outputTokens: number): number {64 return (inputTokens * pricing.inputPerMTok + outputTokens * pricing.outputPerMTok) / 1_000_00065}6667/**68 * Which sampling/control parameters a model accepts. Providers reject requests69 * carrying unsupported parameters, so requests only include what's supported —70 * and the params panel only shows controls that apply.71 */72export interface ParameterSupport {73 temperature: boolean74 topP: boolean75 frequencyPenalty: boolean76 presencePenalty: boolean77 /** Send "max_completion_tokens" instead of "max_tokens". */78 usesMaxCompletionTokens: boolean79 /** Accepts `reasoning_effort`. */80 reasoningEffort: boolean81 /** Anthropic `thinking` / Qwen `enable_thinking` explicit toggle. */82 thinkingToggle: boolean83 /** Model rejects non-streaming calls — `complete` aggregates a stream. */84 requiresStreaming: boolean85}8687/** A chat-capable model offered by a provider. Instances come from the catalog. */88export interface AIModel {89 /** Exact model ID as sent in API requests. */90 id: string91 provider: Provider92 displayName: string93 contextWindow: number94 maxOutputTokens: number | null95 capabilities: ModelCapabilities96 pricing: ModelPricing | null97 parameterSupport: ParameterSupport98 isLegacy: boolean99 isRecommended: boolean100 /** Base URL override for user-defined custom models; undefined for built-ins. */101 customBaseURL?: string102}103104/** Short badge text for the model chip (e.g. "1M ctx"). */105export function contextBadge(model: AIModel): string {106 const ctx = model.contextWindow107 if (ctx >= 1_000_000) return `${Math.floor(ctx / 1_000_000)}M ctx`108 if (ctx >= 1_000) return `${Math.floor(ctx / 1_000)}K ctx`109 return `${ctx} ctx`110}111112/** Token usage reported by a provider for one exchange. */113export interface TokenUsage {114 inputTokens: number115 outputTokens: number116 reasoningTokens?: number117}118119export function addUsage(a: TokenUsage, b: TokenUsage): TokenUsage {120 const reasoning = (a.reasoningTokens ?? 0) + (b.reasoningTokens ?? 0)121 return {122 inputTokens: a.inputTokens + b.inputTokens,123 outputTokens: a.outputTokens + b.outputTokens,124 ...(reasoning > 0 ? { reasoningTokens: reasoning } : {}),125 }126}127128/** One numbered web citation (Perplexity sonar family). */129export interface Citation {130 index: number131 url: string132 title?: string133}134135export type AttachmentKind = 'image' | 'textFile'136137/** An image or text-file attachment on a user message. */138export interface Attachment {139 id: string140 kind: AttachmentKind141 fileName: string142 mimeType: string143 /** Base64 payload (images) or UTF-8 text content (text files). */144 data: string145}146147export type MessageRole = 'user' | 'assistant' | 'system'148149/** One chat turn. */150export interface Message {151 id: string152 role: MessageRole153 text: string154 reasoning?: string155 citations?: Citation[]156 attachments?: Attachment[]157 modelID?: string158 provider?: Provider159 usage?: TokenUsage160 estimatedCost?: number161 errorText?: string162 createdAt: number163 /** Alternate assistant responses (regenerate variants); active one is `text`. */164 variants?: MessageVariant[]165 /** Index of the active variant in `variants`, if any. */166 activeVariant?: number167 pinned?: boolean168 note?: string169}170171/** A stored alternate response for one assistant turn. */172export interface MessageVariant {173 text: string174 reasoning?: string175 citations?: Citation[]176 modelID?: string177 provider?: Provider178 usage?: TokenUsage179 estimatedCost?: number180 createdAt: number181}182183/** Per-conversation sampling parameters. undefined = provider default (omitted). */184export interface ChatParameters {185 temperature?: number186 topP?: number187 maxTokens?: number188 frequencyPenalty?: number189 presencePenalty?: number190 reasoningEffort?: 'low' | 'medium' | 'high'191 thinkingEnabled?: boolean192}193194/** One conversation thread. */195export interface Conversation {196 id: string197 title: string198 createdAt: number199 updatedAt: number200 modelID: string201 provider: Provider202 systemPrompt?: string203 parameters: ChatParameters204 messages: Message[]205 pinned: boolean206 archived?: boolean207 tags?: string[]208 personaID?: string209 /** False once the user manually renames (disables auto-titling). */210 hasAutoTitle: boolean211 /** Conversation this one was branched from, if any. */212 branchedFrom?: { conversationID: string; messageID: string }213}214215export type ThemeMode = 'light' | 'dark' | 'system'216export type AccentChoice = 'indigo' | 'graphite' | 'teal' | 'amber' | 'rose'217export type MessageDensity = 'comfortable' | 'compact'218219/** Global app settings (storage/settings.ts). */220export interface Settings {221 theme: ThemeMode222 accent: AccentChoice223 chatFontSize: number224 density: MessageDensity225 defaultModelID: string226 defaultProvider: Provider227 defaultSystemPrompt: string228 defaultParameters: ChatParameters229 /** Per-provider base-URL overrides (user proxy / Zyquo Router). */230 proxyBaseURLs: Partial<Record<Provider, string>>231 streamingEnabled: boolean232 /** User-defined model aliases (e.g. "fast" → model). */233 aliases: Record<string, { modelID: string; provider: Provider }>234 favoriteModelIDs: string[]235 recentModelIDs: string[]236 firstRunAcknowledged: boolean237 focusMode: boolean238}239