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 * 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