| ---
|
|
|
|
|
| ---
|
|
|
| <script>
|
|
|
| let currentMessageContainer: HTMLDivElement | null = null;
|
| let hideMessageTimer: number | null = null;
|
|
|
|
|
| export function showMessage(message: string, options: { containerId?: string; displayTime?: number } = {}) {
|
|
|
| if (!message || !message.trim()) {
|
| return;
|
| }
|
|
|
|
|
| if (currentMessageContainer) {
|
| if (hideMessageTimer !== null) {
|
| clearTimeout(hideMessageTimer);
|
| }
|
| if (currentMessageContainer.parentNode) {
|
| currentMessageContainer.parentNode.removeChild(currentMessageContainer);
|
| }
|
| currentMessageContainer = null;
|
| }
|
|
|
|
|
| const existingMessages = document.querySelectorAll(
|
| ".model-message-container"
|
| );
|
| existingMessages.forEach((msg) => {
|
| if (msg.parentNode) {
|
| msg.parentNode.removeChild(msg);
|
| }
|
| });
|
|
|
|
|
| const isDarkMode =
|
| document.documentElement.classList.contains("dark") ||
|
| window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
|
|
|
| const messageContainer = document.createElement("div");
|
| messageContainer.className = "model-message-container";
|
|
|
|
|
| const messageEl = document.createElement("div");
|
| messageEl.className = "model-message";
|
| messageEl.textContent = message;
|
|
|
|
|
| const arrowEl = document.createElement("div");
|
| arrowEl.className = "model-message-arrow";
|
|
|
|
|
| Object.assign(messageContainer.style, {
|
| position: "fixed",
|
| zIndex: "1001",
|
| pointerEvents: "none",
|
| opacity: "0",
|
| transform: "translateY(15px) translateX(-50%) scale(0.9)",
|
| transition: "all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)",
|
| });
|
|
|
|
|
| const messageStyles = {
|
| position: "relative",
|
| background: isDarkMode
|
| ? "linear-gradient(135deg, rgba(45, 55, 72, 0.95), rgba(26, 32, 44, 0.9))"
|
| : "linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(240, 248, 255, 0.9))",
|
| color: isDarkMode ? "#e2e8f0" : "#2c3e50",
|
| padding: "12px 16px",
|
| borderRadius: "16px",
|
| fontSize: "14px",
|
| fontWeight: "500",
|
| fontFamily:
|
| '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
| maxWidth: "240px",
|
| minWidth: "100px",
|
| wordWrap: "break-word",
|
| textAlign: "center",
|
| whiteSpace: "pre-wrap",
|
| boxShadow: isDarkMode
|
| ? "0 8px 32px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(0, 0, 0, 0.2)"
|
| : "0 8px 32px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08)",
|
| border: isDarkMode
|
| ? "1px solid rgba(255, 255, 255, 0.1)"
|
| : "1px solid rgba(255, 255, 255, 0.6)",
|
| backdropFilter: "blur(12px)",
|
| letterSpacing: "0.3px",
|
| lineHeight: "1.4",
|
| };
|
| Object.assign(messageEl.style, messageStyles);
|
|
|
|
|
| Object.assign(arrowEl.style, {
|
| position: "absolute",
|
| top: "100%",
|
| left: "50%",
|
| transform: "translateX(-50%)",
|
| width: "0",
|
| height: "0",
|
| borderLeft: "8px solid transparent",
|
| borderRight: "8px solid transparent",
|
| borderTop: isDarkMode
|
| ? "8px solid rgba(45, 55, 72, 0.95)"
|
| : "8px solid rgba(255, 255, 255, 0.95)",
|
| filter: "drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1))",
|
| });
|
|
|
|
|
| messageEl.appendChild(arrowEl);
|
| messageContainer.appendChild(messageEl);
|
|
|
|
|
| document.body.appendChild(messageContainer);
|
| currentMessageContainer = messageContainer;
|
|
|
|
|
| const container = document.getElementById(options.containerId || "model-container");
|
| if (container) {
|
| const rect = container.getBoundingClientRect();
|
|
|
|
|
| const containerCenterX = rect.left + rect.width / 2;
|
|
|
|
|
| const estimatedMessageWidth = 240;
|
| const estimatedMessageHeight = 60;
|
| const screenPadding = 10;
|
|
|
|
|
| let messageX = containerCenterX;
|
| let messageY = rect.top - estimatedMessageHeight - 25;
|
|
|
|
|
| const minX = screenPadding + estimatedMessageWidth / 2;
|
| const maxX =
|
| window.innerWidth - screenPadding - estimatedMessageWidth / 2;
|
|
|
| if (messageX < minX) {
|
| messageX = minX;
|
| } else if (messageX > maxX) {
|
| messageX = maxX;
|
| }
|
|
|
|
|
| const minY = screenPadding;
|
| const maxY = window.innerHeight - estimatedMessageHeight - screenPadding;
|
|
|
| if (messageY < minY) {
|
|
|
| messageY = rect.bottom + 25;
|
|
|
| arrowEl.style.top = "0";
|
| arrowEl.style.bottom = "auto";
|
| arrowEl.style.borderTop = "none";
|
| arrowEl.style.borderBottom = isDarkMode
|
| ? "8px solid rgba(45, 55, 72, 0.95)"
|
| : "8px solid rgba(255, 255, 255, 0.95)";
|
| } else if (messageY > maxY) {
|
| messageY = maxY;
|
| }
|
|
|
|
|
| messageContainer.style.left = messageX + "px";
|
| messageContainer.style.top = messageY + "px";
|
|
|
|
|
| setTimeout(() => {
|
| const actualMessageRect = messageContainer.getBoundingClientRect();
|
| const actualWidth = actualMessageRect.width;
|
| const actualHeight = actualMessageRect.height;
|
|
|
|
|
| let adjustedX = containerCenterX;
|
| const actualMinX = screenPadding + actualWidth / 2;
|
| const actualMaxX = window.innerWidth - screenPadding - actualWidth / 2;
|
|
|
| if (adjustedX < actualMinX) {
|
| adjustedX = actualMinX;
|
| } else if (adjustedX > actualMaxX) {
|
| adjustedX = actualMaxX;
|
| }
|
|
|
|
|
| let adjustedY = rect.top - actualHeight - 25;
|
| const actualMinY = screenPadding;
|
| const actualMaxY = window.innerHeight - actualHeight - screenPadding;
|
| let isAboveModel = true;
|
|
|
| if (adjustedY < actualMinY) {
|
| adjustedY = rect.bottom + 25;
|
| isAboveModel = false;
|
| } else if (adjustedY > actualMaxY) {
|
| adjustedY = actualMaxY;
|
| }
|
|
|
|
|
| const modelCenterX = rect.left + rect.width / 2;
|
| const messageCenterX = adjustedX;
|
| const arrowOffsetX = modelCenterX - messageCenterX;
|
|
|
|
|
| const maxOffset = actualWidth / 2 - 20;
|
| const clampedOffsetX = Math.max(
|
| -maxOffset,
|
| Math.min(maxOffset, arrowOffsetX)
|
| );
|
|
|
|
|
| if (isAboveModel) {
|
|
|
| Object.assign(arrowEl.style, {
|
| position: "absolute",
|
| top: "100%",
|
| left: "50%",
|
| bottom: "auto",
|
| transform: `translateX(calc(-50% + ${clampedOffsetX}px))`,
|
| width: "0",
|
| height: "0",
|
| borderLeft: "8px solid transparent",
|
| borderRight: "8px solid transparent",
|
| borderTop: isDarkMode
|
| ? "8px solid rgba(45, 55, 72, 0.95)"
|
| : "8px solid rgba(255, 255, 255, 0.95)",
|
| borderBottom: "none",
|
| filter: "drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1))",
|
| });
|
| } else {
|
|
|
| Object.assign(arrowEl.style, {
|
| position: "absolute",
|
| top: "0",
|
| left: "50%",
|
| bottom: "auto",
|
| transform: `translateX(calc(-50% + ${clampedOffsetX}px))`,
|
| width: "0",
|
| height: "0",
|
| borderLeft: "8px solid transparent",
|
| borderRight: "8px solid transparent",
|
| borderTop: "none",
|
| borderBottom: isDarkMode
|
| ? "8px solid rgba(45, 55, 72, 0.95)"
|
| : "8px solid rgba(255, 255, 255, 0.95)",
|
| filter: "drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1))",
|
| });
|
| }
|
|
|
|
|
| messageContainer.style.left = adjustedX + "px";
|
| messageContainer.style.top = adjustedY + "px";
|
| }, 50);
|
| }
|
|
|
|
|
| setTimeout(() => {
|
| messageContainer.style.opacity = "1";
|
| messageContainer.style.transform =
|
| "translateY(0) translateX(-50%) scale(1)";
|
| }, 100);
|
|
|
|
|
| const displayTime = options.displayTime || 3000;
|
| hideMessageTimer = window.setTimeout(() => {
|
| messageContainer.style.opacity = "0";
|
| messageContainer.style.transform =
|
| "translateY(-15px) translateX(-50%) scale(0.95)";
|
| setTimeout(() => {
|
| if (messageContainer.parentNode) {
|
| messageContainer.parentNode.removeChild(messageContainer);
|
| }
|
|
|
| if (currentMessageContainer === messageContainer) {
|
| currentMessageContainer = null;
|
| }
|
| }, 400);
|
| }, displayTime);
|
| }
|
|
|
|
|
| export function clearMessage() {
|
| if (currentMessageContainer) {
|
| if (hideMessageTimer !== null) {
|
| clearTimeout(hideMessageTimer);
|
| }
|
| if (currentMessageContainer.parentNode) {
|
| currentMessageContainer.parentNode.removeChild(currentMessageContainer);
|
| }
|
| currentMessageContainer = null;
|
| }
|
|
|
|
|
| const existingMessages = document.querySelectorAll(
|
| ".model-message-container"
|
| );
|
| existingMessages.forEach((msg) => {
|
| if (msg.parentNode) {
|
| msg.parentNode.removeChild(msg);
|
| }
|
| });
|
| }
|
|
|
|
|
| (window as any).showModelMessage = showMessage;
|
| (window as any).clearModelMessage = clearMessage;
|
| </script>
|
|
|