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%
6.4 KB · 239 lines typescript
Raw Blame History
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