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 message = { type: POST_MESSAGE_TYPES.COPY_TO_CLIPBOARD, text, } as const; console.log("[copyToClipboard] called, text length:", text.length); // 始终发送 postMessage,由 sendToParent 内部判断是否为 iframe 环境 // 与 openSkillDialog 等其他 iframe 通信保持一致 try { sendToParent(message); } catch { // no-op } // 同时也尝试直接写剪贴板(非 iframe 场景兜底) try { await navigator.clipboard.writeText(text); } catch { // no-op: 在 iframe 环境下由父窗口处理 } } /** * 计算字符串的视觉宽度(中文算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}`; }