/** * Copilot text transforms — powered by @bytelyst/llm. * * Falls back to local heuristics if LLM is unavailable. */ import { llm } from './llm.js'; export type CopilotAction = 'shorten' | 'expand' | 'bulletize' | 'grammar' | 'fix-rewrite' | 'change-tone' | 'continue' | 'explain'; const SYSTEM_PROMPTS: Record = { shorten: 'Condense the text to about half its length while preserving key points. Return only the shortened text.', expand: 'Expand the text with more detail and examples. Return only the expanded text.', bulletize: 'Convert the text into concise bullet points. Return only the bullet points.', grammar: 'Fix grammar, spelling, and punctuation. Preserve original meaning and tone. Return only the corrected text.', 'fix-rewrite': 'Completely rewrite the text for better clarity, grammar, and flow while preserving the original meaning. Return only the rewritten text.', 'change-tone': 'Rewrite the text in the requested tone (formal, casual, professional, or friendly). The tone is specified at the end after "Tone:". Return only the rewritten text.', 'continue': 'You are a writing assistant. Continue writing naturally from where the text ends. Write 2-3 paragraphs that flow logically from the context. Return only the continuation, not the original text.', 'explain': 'Explain the given term, concept, or text selection concisely in 2-3 sentences. Return only the explanation.', }; function fallbackTransform(action: CopilotAction, text: string): string { const lines = text.split(/\n/).map((l) => l.trim()).filter(Boolean); switch (action) { case 'bulletize': return lines.map((l) => (l.startsWith('-') || l.startsWith('•') ? l : `- ${l}`)).join('\n'); case 'shorten': { const words = text.split(/\s+/); const target = Math.max(8, Math.floor(words.length * 0.55)); return words.slice(0, target).join(' ') + (words.length > target ? '…' : ''); } case 'expand': return `${text}\n\n_Additional detail could be added here to expand on the main points._`; case 'fix-rewrite': return text; case 'change-tone': return text; case 'continue': return `${text}\n\n[Continue writing here...]`; case 'explain': return 'Explanation not available without an LLM provider.'; case 'grammar': default: return text; } } export async function runCopilotTransform(action: CopilotAction, text: string): Promise { const provider = llm(); if (!provider.isConfigured()) { return fallbackTransform(action, text); } try { const result = await provider.chatCompletion({ messages: [ { role: 'system', content: SYSTEM_PROMPTS[action] }, { role: 'user', content: text }, ], temperature: 0.3, maxTokens: 4096, }); const out = result.content.trim(); if (out.length > 0) return out; } catch { // fall through to local heuristics } return fallbackTransform(action, text); } export async function suggestTitleFromBody(body: string): Promise { const plain = body.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim(); const provider = llm(); if (!provider.isConfigured()) { return plain.split(/[.!?]/)[0]?.trim().slice(0, 80) || 'Untitled note'; } try { const result = await provider.chatCompletion({ messages: [ { role: 'system', content: 'Suggest a concise, descriptive title (max 8 words). Return only the title, no quotes.' }, { role: 'user', content: plain.slice(0, 4000) }, ], temperature: 0.6, maxTokens: 64, }); const t = result.content.trim(); if (t.length > 0 && t.length < 500) return t; } catch { // fall through } return plain.split(/[.!?]/)[0]?.trim().slice(0, 80) || 'Untitled note'; }