/** * `string → ChatAttachment` — the producer for "Pasted text" chunks. * * When a long paste is converted into an attachment chip instead of * being dumped into the textarea (ChatGPT/Claude behaviour), the raw * text is minted into a `type:'text'` attachment. Its payload lives in * `text` (exact, newline-preserving); `url` is empty since there is no * blob backing it. * * Mirrors `fileToAttachment` so the picker, drag-drop, file-paste and * text-paste paths all funnel into the same `ChatAttachment` shape. */ import { createId } from '../core/ids'; import type { ChatAttachment } from '../types'; /** Default character threshold above which a paste becomes a chunk. */ export const DEFAULT_PASTE_TEXT_THRESHOLD = 2000; /** Max length of the auto-derived title (first non-empty line). */ const TITLE_MAX = 32; /** * Derive a short, human-readable title from the pasted text — the first * non-empty line, collapsed and ellipsised (e.g. "Valuing the Bali Vec…"). * Falls back to a generic label for whitespace-only / empty input. */ export function deriveTextTitle(text: string): string { const firstLine = text .split(/\r?\n/) .map((l) => l.trim()) .find((l) => l.length > 0); if (!firstLine) return 'Pasted text'; // Strip leading markdown markers (heading `#`, blockquote `>`, list // bullets `-`/`*`/`+`) so a markdown paste reads as plain text on the // chip — the title is a label, not source. The payload is untouched. const stripped = firstLine .replace(/^#{1,6}\s+/, '') .replace(/^>\s+/, '') .replace(/^[-*+]\s+/, '') .trim(); // Collapse internal runs of whitespace so a title never wraps oddly. const collapsed = (stripped || firstLine).replace(/\s+/g, ' '); return collapsed.length > TITLE_MAX ? `${collapsed.slice(0, TITLE_MAX).trimEnd()}…` : collapsed; } /** * Convert a pasted string into a `type:'text'` `ChatAttachment`. * * - `name` — short title derived from the first line (shown on the chip). * - `text` — the full, exact payload (sent to `onSubmit` alongside the * message content). * - `url` — empty: text chunks have no blob backing. * - `sizeBytes` — UTF-8 byte length, for an optional "N chars / KB" hint. */ export function textToAttachment(text: string): ChatAttachment { return { id: createId('att'), type: 'text', url: '', name: deriveTextTitle(text), mimeType: 'text/plain', sizeBytes: new Blob([text]).size, status: 'ready', text, }; }