deerflow2/frontend/src/lib/utils.ts

90 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<void> {
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}`;
}