| | import { toast } from 'svelte-sonner';
|
| | import { AttachmentType } from '$lib/enums';
|
| | import type {
|
| | DatabaseMessageExtra,
|
| | DatabaseMessageExtraTextFile,
|
| | DatabaseMessageExtraLegacyContext,
|
| | ClipboardTextAttachment,
|
| | ParsedClipboardContent
|
| | } from '$lib/types';
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | export async function copyToClipboard(
|
| | text: string,
|
| | successMessage = 'Copied to clipboard',
|
| | errorMessage = 'Failed to copy to clipboard'
|
| | ): Promise<boolean> {
|
| | try {
|
| |
|
| | if (navigator.clipboard && navigator.clipboard.writeText) {
|
| | await navigator.clipboard.writeText(text);
|
| | toast.success(successMessage);
|
| | return true;
|
| | }
|
| |
|
| |
|
| | const textArea = document.createElement('textarea');
|
| | textArea.value = text;
|
| | textArea.style.position = 'fixed';
|
| | textArea.style.left = '-999999px';
|
| | textArea.style.top = '-999999px';
|
| | document.body.appendChild(textArea);
|
| | textArea.focus();
|
| | textArea.select();
|
| |
|
| | const successful = document.execCommand('copy');
|
| | document.body.removeChild(textArea);
|
| |
|
| | if (successful) {
|
| | toast.success(successMessage);
|
| | return true;
|
| | } else {
|
| | throw new Error('execCommand failed');
|
| | }
|
| | } catch (error) {
|
| | console.error('Failed to copy to clipboard:', error);
|
| | toast.error(errorMessage);
|
| | return false;
|
| | }
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | export async function copyCodeToClipboard(
|
| | rawCode: string,
|
| | successMessage = 'Code copied to clipboard',
|
| | errorMessage = 'Failed to copy code'
|
| | ): Promise<boolean> {
|
| | return copyToClipboard(rawCode, successMessage, errorMessage);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | export function formatMessageForClipboard(
|
| | content: string,
|
| | extras?: DatabaseMessageExtra[],
|
| | asPlainText: boolean = false
|
| | ): string {
|
| |
|
| | const textAttachments =
|
| | extras?.filter(
|
| | (extra): extra is DatabaseMessageExtraTextFile | DatabaseMessageExtraLegacyContext =>
|
| | extra.type === AttachmentType.TEXT || extra.type === AttachmentType.LEGACY_CONTEXT
|
| | ) ?? [];
|
| |
|
| | if (textAttachments.length === 0) {
|
| | return content;
|
| | }
|
| |
|
| | if (asPlainText) {
|
| | const parts = [content];
|
| | for (const att of textAttachments) {
|
| | parts.push(att.content);
|
| | }
|
| | return parts.join('\n\n');
|
| | }
|
| |
|
| | const clipboardAttachments: ClipboardTextAttachment[] = textAttachments.map((att) => ({
|
| | type: AttachmentType.TEXT,
|
| | name: att.name,
|
| | content: att.content
|
| | }));
|
| |
|
| | return `${JSON.stringify(content)}\n${JSON.stringify(clipboardAttachments, null, 2)}`;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | export function parseClipboardContent(clipboardText: string): ParsedClipboardContent {
|
| | const defaultResult: ParsedClipboardContent = {
|
| | message: clipboardText,
|
| | textAttachments: []
|
| | };
|
| |
|
| | if (!clipboardText.startsWith('"')) {
|
| | return defaultResult;
|
| | }
|
| |
|
| | try {
|
| | let stringEndIndex = -1;
|
| | let escaped = false;
|
| |
|
| | for (let i = 1; i < clipboardText.length; i++) {
|
| | const char = clipboardText[i];
|
| |
|
| | if (escaped) {
|
| | escaped = false;
|
| | continue;
|
| | }
|
| |
|
| | if (char === '\\') {
|
| | escaped = true;
|
| | continue;
|
| | }
|
| |
|
| | if (char === '"') {
|
| | stringEndIndex = i;
|
| | break;
|
| | }
|
| | }
|
| |
|
| | if (stringEndIndex === -1) {
|
| | return defaultResult;
|
| | }
|
| |
|
| | const jsonStringPart = clipboardText.substring(0, stringEndIndex + 1);
|
| | const remainingPart = clipboardText.substring(stringEndIndex + 1).trim();
|
| |
|
| | const message = JSON.parse(jsonStringPart) as string;
|
| |
|
| | if (!remainingPart || !remainingPart.startsWith('[')) {
|
| | return {
|
| | message,
|
| | textAttachments: []
|
| | };
|
| | }
|
| |
|
| | const attachments = JSON.parse(remainingPart) as unknown[];
|
| |
|
| | const validAttachments: ClipboardTextAttachment[] = [];
|
| |
|
| | for (const att of attachments) {
|
| | if (isValidTextAttachment(att)) {
|
| | validAttachments.push({
|
| | type: AttachmentType.TEXT,
|
| | name: att.name,
|
| | content: att.content
|
| | });
|
| | }
|
| | }
|
| |
|
| | return {
|
| | message,
|
| | textAttachments: validAttachments
|
| | };
|
| | } catch {
|
| | return defaultResult;
|
| | }
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| | function isValidTextAttachment(
|
| | obj: unknown
|
| | ): obj is { type: string; name: string; content: string } {
|
| | if (typeof obj !== 'object' || obj === null) {
|
| | return false;
|
| | }
|
| |
|
| | const record = obj as Record<string, unknown>;
|
| |
|
| | return (
|
| | (record.type === AttachmentType.TEXT || record.type === 'TEXT') &&
|
| | typeof record.name === 'string' &&
|
| | typeof record.content === 'string'
|
| | );
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| | export function hasClipboardAttachments(clipboardText: string): boolean {
|
| | if (!clipboardText.startsWith('"')) {
|
| | return false;
|
| | }
|
| |
|
| | const parsed = parseClipboardContent(clipboardText);
|
| | return parsed.textAttachments.length > 0;
|
| | }
|
| |
|