char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Aleksander Grygierui: Linting & Formatting scripts (#26819)92d1bb0c9

char/aliceai-t5-moe
8.2 KiB322 linesraw
1import { AttachmentType } from '$lib/enums';
2import type {
3	ClipboardAttachment,
4	ClipboardMcpPromptAttachment,
5	ClipboardTextAttachment,
6	DatabaseMessageExtra,
7	DatabaseMessageExtraLegacyContext,
8	DatabaseMessageExtraMcpPrompt,
9	DatabaseMessageExtraMcpResource,
10	DatabaseMessageExtraTextFile,
11	ParsedClipboardContent
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(
24	text: string,
25	successMessage = 'Copied to clipboard',
26	errorMessage = 'Failed to copy to clipboard'
27): Promise<boolean> {
28	try {
29		// Try modern clipboard API first (secure contexts only)
30		if (navigator.clipboard && navigator.clipboard.writeText) {
31			await navigator.clipboard.writeText(text);
32			toast.success(successMessage);
33
34			return true;
35		}
36
37		// Fallback for non-secure contexts
38		const textArea = document.createElement('textarea');
39
40		textArea.value = text;
41		textArea.style.position = 'fixed';
42		textArea.style.left = '-999999px';
43		textArea.style.top = '-999999px';
44		document.body.appendChild(textArea);
45		textArea.focus();
46		textArea.select();
47
48		const successful = document.execCommand('copy');
49
50		document.body.removeChild(textArea);
51
52		if (successful) {
53			toast.success(successMessage);
54
55			return true;
56		} else {
57			throw new Error('execCommand failed');
58		}
59	} catch (error) {
60		console.error('Failed to copy to clipboard:', error);
61		toast.error(errorMessage);
62
63		return 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(
75	rawCode: string,
76	successMessage = 'Code copied to clipboard',
77	errorMessage = 'Failed to copy code'
78): Promise<boolean> {
79	return 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(
109	content: string,
110	extras?: DatabaseMessageExtra[],
111	asPlainText: boolean = false
112): string {
113	// Filter text-like attachments (TEXT, LEGACY_CONTEXT, MCP_PROMPT, and MCP_RESOURCE types)
114	const textAttachments =
115		extras?.filter(
116			(
117				extra
118			): extra is
119				| DatabaseMessageExtraTextFile
120				| DatabaseMessageExtraLegacyContext
121				| DatabaseMessageExtraMcpPrompt
122				| DatabaseMessageExtraMcpResource =>
123				extra.type === AttachmentType.TEXT ||
124				extra.type === AttachmentType.LEGACY_CONTEXT ||
125				extra.type === AttachmentType.MCP_PROMPT ||
126				extra.type === AttachmentType.MCP_RESOURCE
127		) ?? [];
128
129	if (textAttachments.length === 0) {
130		return content;
131	}
132
133	if (asPlainText) {
134		const parts = [content];
135
136		for (const att of textAttachments) {
137			parts.push(att.content);
138		}
139
140		return parts.join('\n\n');
141	}
142
143	const clipboardAttachments: ClipboardAttachment[] = textAttachments.map((att) => {
144		if (att.type === AttachmentType.MCP_PROMPT) {
145			const mcpAtt = att as DatabaseMessageExtraMcpPrompt;
146
147			return {
148				arguments: mcpAtt.arguments,
149				content: mcpAtt.content,
150				name: mcpAtt.name,
151				promptName: mcpAtt.promptName,
152				serverName: mcpAtt.serverName,
153				type: AttachmentType.MCP_PROMPT
154			} as ClipboardMcpPromptAttachment;
155		}
156
157		return {
158			content: att.content,
159			name: att.name,
160			type: AttachmentType.TEXT
161		} as ClipboardTextAttachment;
162	});
163
164	return `${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 {
175	const defaultResult: ParsedClipboardContent = {
176		mcpPromptAttachments: [],
177		message: clipboardText,
178		textAttachments: []
179	};
180
181	if (!clipboardText.startsWith('"')) {
182		return defaultResult;
183	}
184
185	try {
186		let stringEndIndex = -1;
187		let escaped = false;
188
189		for (let i = 1; i < clipboardText.length; i++) {
190			const char = clipboardText[i];
191
192			if (escaped) {
193				escaped = false;
194
195				continue;
196			}
197
198			if (char === '\\') {
199				escaped = true;
200
201				continue;
202			}
203
204			if (char === '"') {
205				stringEndIndex = i;
206
207				break;
208			}
209		}
210
211		if (stringEndIndex === -1) {
212			return defaultResult;
213		}
214
215		const jsonStringPart = clipboardText.substring(0, stringEndIndex + 1);
216		const remainingPart = clipboardText.substring(stringEndIndex + 1).trim();
217		const message = JSON.parse(jsonStringPart) as string;
218
219		if (!remainingPart || !remainingPart.startsWith('[')) {
220			return {
221				mcpPromptAttachments: [],
222				message,
223				textAttachments: []
224			};
225		}
226
227		const attachments = JSON.parse(remainingPart) as unknown[];
228		const validTextAttachments: ClipboardTextAttachment[] = [];
229		const validMcpPromptAttachments: ClipboardMcpPromptAttachment[] = [];
230
231		for (const att of attachments) {
232			if (isValidMcpPromptAttachment(att)) {
233				validMcpPromptAttachments.push({
234					arguments: att.arguments,
235					content: att.content,
236					name: att.name,
237					promptName: att.promptName,
238					serverName: att.serverName,
239					type: AttachmentType.MCP_PROMPT
240				});
241			} else if (isValidTextAttachment(att)) {
242				validTextAttachments.push({
243					content: att.content,
244					name: att.name,
245					type: AttachmentType.TEXT
246				});
247			}
248		}
249
250		return {
251			mcpPromptAttachments: validMcpPromptAttachments,
252			message,
253			textAttachments: validTextAttachments
254		};
255	} catch {
256		return 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 {
266	type: string;
267	name: string;
268	serverName: string;
269	promptName: string;
270	content: string;
271	arguments?: Record<string, string>;
272} {
273	if (typeof obj !== 'object' || obj === null) {
274		return false;
275	}
276
277	const record = obj as Record<string, unknown>;
278
279	return (
280		(record.type === AttachmentType.MCP_PROMPT || record.type === 'MCP_PROMPT') &&
281		typeof record.name === 'string' &&
282		typeof record.serverName === 'string' &&
283		typeof record.promptName === 'string' &&
284		typeof 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(
294	obj: unknown
295): obj is { type: string; name: string; content: string } {
296	if (typeof obj !== 'object' || obj === null) {
297		return false;
298	}
299
300	const record = obj as Record<string, unknown>;
301
302	return (
303		(record.type === AttachmentType.TEXT || record.type === 'TEXT') &&
304		typeof record.name === 'string' &&
305		typeof 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 {
315	if (!clipboardText.startsWith('"')) {
316		return false;
317	}
318
319	const parsed = parseClipboardContent(clipboardText);
320
321	return parsed.textAttachments.length > 0 || parsed.mcpPromptAttachments.length > 0;
322}