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%
3.3 KB · 95 lines typescript
Raw Blame History
1/*2 *  conversationTools.ts3 *  Zyquo Cloud Web4 *5 *  Author: Simon-Pierre Boucher6 *  Mail: contact@spboucher.ai7 *8 *  Conversation-level tools: summarize (via the conversation's provider using9 *  its cheapest model) and context auto-trim (drop oldest turns, keep the10 *  system prompt, when near the window limit).11 */1213import { cheapestModel, estimateTokens, findModel } from './catalogHelpers'14import { clientFor } from '../providers/registry'15import { uid, useStore } from '../state/store'16import { getKey } from '../storage/keys'17import type { Conversation, Message } from '../types'1819/** Appends an assistant summary turn produced by the provider's cheapest model. */20export async function summarizeConversation(conversationID: string): Promise<void> {21  const state = useStore.getState()22  const conversation = state.conversations.find((c) => c.id === conversationID)23  if (!conversation || conversation.messages.length === 0) return24  const model =25    cheapestModel(conversation.provider) ?? findModel(conversation.provider, conversation.modelID)26  const apiKey = model ? getKey(model.provider) : undefined27  if (!model || !apiKey) {28    state.toast('No key available to summarize with', 'error')29    return30  }31  state.toast('Summarizing conversation…', 'info')32  const transcript = conversation.messages33    .filter((m) => !m.errorText)34    .map((m) => `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.text.slice(0, 1500)}`)35    .join('\n\n')36  try {37    const result = await clientFor(model).complete(38      {39        model,40        messages: [41          {42            id: uid(),43            role: 'user',44            text: `Summarize this conversation in a concise bullet list (key points, decisions, open questions):\n\n${transcript.slice(0, 24_000)}`,45            createdAt: Date.now(),46          },47        ],48        parameters: { maxTokens: 600 },49        stream: false,50      },51      apiKey52    )53    const summary: Message = {54      id: uid(),55      role: 'assistant',56      text: `**Conversation summary**\n\n${result.text}`,57      modelID: model.id,58      provider: model.provider,59      createdAt: Date.now(),60    }61    state.updateConversation(conversationID, {62      messages: [...conversation.messages, summary],63    })64  } catch (err) {65    state.toast(err instanceof Error ? err.message : 'Summarize failed', 'error')66  }67}6869/**70 * Auto-trim: when the estimated context usage exceeds ~90% of the model's71 * window, drops the oldest non-system turns (keeping the most recent ones)72 * and returns the trimmed message list for the request.73 */74export function autoTrimForContext(conversation: Conversation): {75  messages: Message[]76  trimmed: number77} {78  const model = findModel(conversation.provider, conversation.modelID)79  if (!model) return { messages: conversation.messages, trimmed: 0 }80  const budget = model.contextWindow * 0.981  const systemTokens = estimateTokens(conversation.systemPrompt ?? '')82  const usable = conversation.messages.filter((m) => !m.errorText)83  let total = systemTokens + usable.reduce((sum, m) => sum + estimateTokens(m.text), 0)84  if (total <= budget) return { messages: conversation.messages, trimmed: 0 }85  const kept = [...usable]86  let trimmed = 087  while (total > budget && kept.length > 2) {88    const dropped = kept.shift()89    if (!dropped) break90    total -= estimateTokens(dropped.text)91    trimmed++92  }93  return { messages: kept, trimmed }94}95