char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
92d1bb0c9
char/aliceai-t5-moe
1import { AttachmentType } from '$lib/enums' ; 2import type { 3ClipboardAttachment , 4ClipboardMcpPromptAttachment , 5ClipboardTextAttachment , 6DatabaseMessageExtra , 7DatabaseMessageExtraLegacyContext , 8DatabaseMessageExtraMcpPrompt , 9DatabaseMessageExtraMcpResource , 10DatabaseMessageExtraTextFile , 11ParsedClipboardContent 12} from '$lib/types' ; 13import { toast } from 'svelte-sonner' ; 14 15/** 16* Copy text to clipboard with toast notification 17* Uses modern clipboard API when available, falls back to legacy method for non-secure contexts 18* @param text - Text to copy to clipboard 19* @param successMessage - Custom success message (optional) 20* @param errorMessage - Custom error message (optional) 21* @returns Promise<boolean> - True if successful, false otherwise 22*/ 23export async function copyToClipboard ( 24text :string , 25successMessage = 'Copied to clipboard' , 26errorMessage = 'Failed to copy to clipboard' 27) :Promise < boolean > { 28try { 29// Try modern clipboard API first (secure contexts only) 30if ( navigator . clipboard && navigator . clipboard . writeText ) { 31await navigator . clipboard . writeText ( text ); 32toast . success ( successMessage ); 33 34return true ; 35} 36 37// Fallback for non-secure contexts 38const textArea = document . createElement ( 'textarea' ); 39 40textArea . value = text ; 41textArea . style . position = 'fixed' ; 42textArea . style . left = '-999999px' ; 43textArea . style . top = '-999999px' ; 44document . body . appendChild ( textArea ); 45textArea . focus (); 46textArea . select (); 47 48const successful = document . execCommand ( 'copy' ); 49 50document . body . removeChild ( textArea ); 51 52if ( successful ) { 53toast . success ( successMessage ); 54 55return true ; 56} else { 57throw new Error ( 'execCommand failed' ); 58} 59} catch ( error ) { 60console . error ( 'Failed to copy to clipboard:' , error ); 61toast . error ( errorMessage ); 62 63return false ; 64} 65} 66 67/** 68* Copy code with HTML entity decoding and toast notification 69* @param rawCode - Raw code string that may contain HTML entities 70* @param successMessage - Custom success message (optional) 71* @param errorMessage - Custom error message (optional) 72* @returns Promise<boolean> - True if successful, false otherwise 73*/ 74export async function copyCodeToClipboard ( 75rawCode :string , 76successMessage = 'Code copied to clipboard' , 77errorMessage = 'Failed to copy code' 78) :Promise < boolean > { 79return copyToClipboard ( rawCode , successMessage , errorMessage ); 80} 81 82/** 83* Formats a message with text attachments for clipboard copying. 84* 85* Default format (asPlainText = false): 86* ``` 87* "Text message content" 88* [ 89* {"type":"TEXT","name":"filename.txt","content":"..."}, 90* {"type":"TEXT","name":"another.txt","content":"..."} 91* ] 92* ``` 93* 94* Plain text format (asPlainText = true): 95* ``` 96* Text message content 97* 98* file content here 99* 100* another file content 101* ``` 102* 103* @param content - The message text content 104* @param extras - Optional array of message attachments 105* @param asPlainText - If true, format as plain text without JSON structure 106* @returns Formatted string for clipboard 107*/ 108export function formatMessageForClipboard ( 109content :string , 110extras ?:DatabaseMessageExtra [], 111asPlainText :boolean = false 112) :string { 113// Filter text-like attachments (TEXT, LEGACY_CONTEXT, MCP_PROMPT, and MCP_RESOURCE types) 114const textAttachments = 115extras ?. filter ( 116( 117extra 118) :extra is 119| DatabaseMessageExtraTextFile 120| DatabaseMessageExtraLegacyContext 121| DatabaseMessageExtraMcpPrompt 122| DatabaseMessageExtraMcpResource => 123extra . type === AttachmentType . TEXT || 124extra . type === AttachmentType . LEGACY_CONTEXT || 125extra . type === AttachmentType . MCP_PROMPT || 126extra . type === AttachmentType . MCP_RESOURCE 127) ?? []; 128 129if ( textAttachments . length === 0 ) { 130return content ; 131} 132 133if ( asPlainText ) { 134const parts = [ content ]; 135 136for ( const att of textAttachments ) { 137parts . push ( att . content ); 138} 139 140return parts . join ( '\n\n' ); 141} 142 143const clipboardAttachments :ClipboardAttachment [] = textAttachments . map (( att ) => { 144if ( att . type === AttachmentType . MCP_PROMPT ) { 145const mcpAtt = att as DatabaseMessageExtraMcpPrompt ; 146 147return { 148arguments :mcpAtt . arguments , 149content :mcpAtt . content , 150name :mcpAtt . name , 151promptName :mcpAtt . promptName , 152serverName :mcpAtt . serverName , 153type :AttachmentType . MCP_PROMPT 154} as ClipboardMcpPromptAttachment ; 155} 156 157return { 158content :att . content , 159name :att . name , 160type :AttachmentType . TEXT 161} as ClipboardTextAttachment ; 162}); 163 164return ` ${ JSON . stringify ( content )} \n ${ JSON . stringify ( clipboardAttachments , null , 2 )} ` ; 165} 166 167/** 168* Parses clipboard content to extract message and text attachments. 169* Supports both plain text and the special format with attachments. 170* 171* @param clipboardText - Raw text from clipboard 172* @returns Parsed content with message and attachments 173*/ 174export function parseClipboardContent ( clipboardText :string ) :ParsedClipboardContent { 175const defaultResult :ParsedClipboardContent = { 176mcpPromptAttachments :[], 177message :clipboardText , 178textAttachments :[] 179}; 180 181if ( ! clipboardText . startsWith ( '"' )) { 182return defaultResult ; 183} 184 185try { 186let stringEndIndex = - 1 ; 187let escaped = false ; 188 189for ( let i = 1 ; i < clipboardText . length ; i ++ ) { 190const char = clipboardText [ i ]; 191 192if ( escaped ) { 193escaped = false ; 194 195continue ; 196} 197 198if ( char === '\\' ) { 199escaped = true ; 200 201continue ; 202} 203 204if ( char === '"' ) { 205stringEndIndex = i ; 206 207break ; 208} 209} 210 211if ( stringEndIndex === - 1 ) { 212return defaultResult ; 213} 214 215const jsonStringPart = clipboardText . substring ( 0 , stringEndIndex + 1 ); 216const remainingPart = clipboardText . substring ( stringEndIndex + 1 ). trim (); 217const message = JSON . parse ( jsonStringPart ) as string ; 218 219if ( ! remainingPart || ! remainingPart . startsWith ( '[' )) { 220return { 221mcpPromptAttachments :[], 222 message, 223textAttachments :[] 224}; 225} 226 227const attachments = JSON . parse ( remainingPart ) as unknown []; 228const validTextAttachments :ClipboardTextAttachment [] = []; 229const validMcpPromptAttachments :ClipboardMcpPromptAttachment [] = []; 230 231for ( const att of attachments ) { 232if ( isValidMcpPromptAttachment ( att )) { 233validMcpPromptAttachments . push ({ 234arguments :att . arguments , 235content :att . content , 236name :att . name , 237promptName :att . promptName , 238serverName :att . serverName , 239type :AttachmentType . MCP_PROMPT 240}); 241} else if ( isValidTextAttachment ( att )) { 242validTextAttachments . push ({ 243content :att . content , 244name :att . name , 245type :AttachmentType . TEXT 246}); 247} 248} 249 250return { 251mcpPromptAttachments :validMcpPromptAttachments , 252 message, 253textAttachments :validTextAttachments 254}; 255} catch { 256return defaultResult ; 257} 258} 259 260/** 261* Type guard to validate an MCP prompt attachment object 262* @param obj The object to validate 263* @returns true if the object is a valid MCP prompt attachment 264*/ 265function isValidMcpPromptAttachment ( obj :unknown ) :obj is{ 266type :string ; 267name :string ; 268serverName :string ; 269promptName :string ; 270content :string ; 271arguments ?:Record < string , string >; 272} { 273if ( typeof obj !== 'object' || obj === null ) { 274return false ; 275} 276 277const record = obj as Record < string , unknown >; 278 279return ( 280( record . type === AttachmentType . MCP_PROMPT || record . type === 'MCP_PROMPT' ) && 281typeof record . name === 'string' && 282typeof record . serverName === 'string' && 283typeof record . promptName === 'string' && 284typeof record . content === 'string' 285); 286} 287 288/** 289* Type guard to validate a text attachment object 290* @param obj The object to validate 291* @returns true if the object is a valid text attachment 292*/ 293function isValidTextAttachment ( 294obj :unknown 295) :obj is{ type :string ; name :string ; content :string } { 296if ( typeof obj !== 'object' || obj === null ) { 297return false ; 298} 299 300const record = obj as Record < string , unknown >; 301 302return ( 303( record . type === AttachmentType . TEXT || record . type === 'TEXT' ) && 304typeof record . name === 'string' && 305typeof record . content === 'string' 306); 307} 308 309/** 310* Checks if clipboard content contains our special format with attachments 311* @param clipboardText - Raw text from clipboard 312* @returns true if the clipboard content contains our special format with attachments 313*/ 314export function hasClipboardAttachments ( clipboardText :string ) :boolean { 315if ( ! clipboardText . startsWith ( '"' )) { 316return false ; 317} 318 319const parsed = parseClipboardContent ( clipboardText ); 320 321return parsed . textAttachments . length > 0 || parsed . mcpPromptAttachments . length > 0 ; 322}