import { clsx, type ClassValue } from "clsx"; import { twMerge } from "tailwind-merge"; import { POST_MESSAGE_TYPES, sendToParent } from "@/core/iframe-messages"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } /** Shared class for external links (underline by default). */ export const externalLinkClass = "text-primary underline underline-offset-2 hover:no-underline"; /** Link style without underline by default (e.g. for streaming/loading). */ export const externalLinkClassNoUnderline = "text-primary hover:underline"; /** * Copy text to clipboard, using postMessage when in iframe. * In iframe context, sends message to parent window to handle clipboard operation. */ export async function copyToClipboard(text: string): Promise { const isInIframe = window.self !== window.top; const message = { type: POST_MESSAGE_TYPES.COPY_TO_CLIPBOARD, text, } as const; if (isInIframe) { try { // Request parent window to copy sendToParent(message); console.log( "[copyToClipboard] iframe mode → postMessage to parent", message, ); return; } catch (error) { console.warn("[copyToClipboard] iframe postMessage failed", error); } } // Direct clipboard access when not in iframe console.log("[copyToClipboard] direct mode", message); await navigator.clipboard.writeText(text); } /** * 计算字符串的视觉宽度(中文算2,英文算1) */ export function getVisualWidth(text: string): number { let width = 0; for (const char of text) { // 中文字符范围:\u4e00-\u9fff(基本汉字) width += /[\u4e00-\u9fff]/.test(char) ? 2 : 1; } return width; } /** * 截断字符串中间部分,保留开头和结尾 * 例如: "very-long-file-name.txt" -> "very-lon...me.txt" * 中文按视觉宽度计算(中文算2,英文算1) */ export function truncateMiddle(text: string, maxVisualWidth = 30): string { const visualWidth = getVisualWidth(text); if (visualWidth <= maxVisualWidth) return text; const startWidth = Math.ceil(maxVisualWidth * 0.6); const endWidth = Math.floor(maxVisualWidth * 0.4) - 3; // -3 for "..." let startPart = ""; let currentWidth = 0; for (const char of text) { const charWidth = /[\u4e00-\u9fff]/.test(char) ? 2 : 1; if (currentWidth + charWidth > startWidth) break; startPart += char; currentWidth += charWidth; } let endPart = ""; currentWidth = 0; for (let i = text.length - 1; i >= 0; i--) { const char = text[i]!; const charWidth = /[\u4e00-\u9fff]/.test(char) ? 2 : 1; if (currentWidth + charWidth > endWidth) break; endPart = char + endPart; currentWidth += charWidth; } return `${startPart}...${endPart}`; }