DeepZer Flows Compact Sender TEMP

Versión temporal exclusiva para DeepZer Flows: conserva el mensaje completo y muestra la burbuja enviada como Fix error.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         DeepZer Flows Compact Sender TEMP
// @namespace    https://deepzerr.com/lovable-compact-sender/deepzer-flows
// @version      1.0.0
// @license      MIT
// @description  Versión temporal exclusiva para DeepZer Flows: conserva el mensaje completo y muestra la burbuja enviada como Fix error.
// @author       DeepZer
// @match        https://lovable.dev/projects/cec453fc-d962-4e70-817b-a68f4bcfe8bb*
// @run-at       document-start
// @noframes
// @sandbox      raw
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  const VERSION = '1.0.0';
  const PROJECT_ID = 'cec453fc-d962-4e70-817b-a68f4bcfe8bb';
  const STATUS_ID = 'dzf-compact-status';
  const STYLE_ID = 'dzf-compact-styles';
  const FORM_SELECTOR = 'form#chat-input';
  const FETCH_MARKER = '__dzfCompactFetchVersion';
  const BOOT_MARKER = '__dzfCompactBootVersion';
  const VISUAL_TIMEOUT_MS = 12000;
  const CHIP_TEXT = 'Fix error';

  if (!location.pathname.includes(`/projects/${PROJECT_ID}`)) return;
  if (window[BOOT_MARKER] === VERSION) return;

  window[BOOT_MARKER] = VERSION;

  function log(...args) {
    console.log(`[DeepZer Flows Compact v${VERSION}]`, ...args);
  }

  function warn(...args) {
    console.warn(`[DeepZer Flows Compact v${VERSION}]`, ...args);
  }

  function normalizeText(value) {
    return String(value || '')
      .replace(/\u00a0/g, ' ')
      .replace(/\s+/g, ' ')
      .trim();
  }

  function isChatRequest(input, init) {
    const method = String(
      init?.method ||
      (input instanceof Request ? input.method : 'GET')
    ).toUpperCase();

    if (method !== 'POST') return false;

    const rawUrl =
      typeof input === 'string'
        ? input
        : input instanceof URL
          ? input.href
          : input?.url || '';

    try {
      const url = new URL(rawUrl, location.href);

      const lovableHost =
        url.hostname === 'lovable.dev' ||
        url.hostname.endsWith('.lovable.dev');

      return lovableHost && url.pathname.includes('/chat');
    } catch (_) {
      const value = String(rawUrl);

      return (
        value.includes('lovable.dev') &&
        value.includes('/chat')
      );
    }
  }

  function decoratePayload(payload) {
    if (
      !payload ||
      typeof payload !== 'object' ||
      typeof payload.message !== 'string' ||
      !payload.message.trim()
    ) {
      return null;
    }

    const originalMessage = payload.message;
    const buildState = window.__lcsBuildState || {};
    const fallbackId = String(payload.id || '');
    const eventId = String(buildState.eventId || fallbackId);

    /*
     * El texto original no se modifica.
     * Lovable recibe la instrucción completa.
     */
    payload.intent = 'fix_error';

    payload.message_intent_metadata = {
      fix_error_metadata: {
        errors: [
          {
            error_type: 'build',
            error_message: String(
              buildState.errorMessage || originalMessage
            ),
            build_event_id: eventId
          }
        ]
      }
    };

    payload.contains_error = true;
    payload.error_source = 'build_errors';
    payload.error_ids = eventId ? [eventId] : [];

    return {
      payload,
      originalMessage
    };
  }

  function modifyStringBody(body) {
    if (typeof body !== 'string') return null;

    try {
      const payload = JSON.parse(body);
      const result = decoratePayload(payload);

      if (!result) return null;

      return {
        body: JSON.stringify(result.payload),
        originalMessage: result.originalMessage
      };
    } catch (_) {
      return null;
    }
  }

  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;

    const style = document.createElement('style');

    style.id = STYLE_ID;

    style.textContent = `
      #${STATUS_ID} {
        display: flex;
        align-items: center;
        gap: 7px;
        width: max-content;
        margin: 6px 0 0 8px;
        padding: 0;
        border: 0 !important;
        background: transparent !important;
        box-shadow: none !important;
        color: #8a8a93;
        font: 500 11px/1 Inter, system-ui, -apple-system,
              BlinkMacSystemFont, "Segoe UI", sans-serif;
        pointer-events: none;
        user-select: none;
      }

      #${STATUS_ID} .dzf-status-dot {
        width: 7px;
        height: 7px;
        flex: 0 0 7px;
        border-radius: 999px;
        background: #22c55e;
        animation: dzf-status-pulse 1.45s ease-in-out infinite;
      }

      .dzf-fix-chip {
        display: inline-flex !important;
        align-items: center !important;
        justify-content: center !important;
        width: max-content !important;
        min-width: 0 !important;
        max-width: 180px !important;
        min-height: 32px !important;
        padding: 7px 13px !important;
        border: 1px solid rgba(148, 163, 184, 0.22) !important;
        border-radius: 999px !important;
        background: rgba(44, 44, 46, 0.96) !important;
        color: #d7d7dc !important;
        font-size: 13px !important;
        font-style: italic !important;
        font-weight: 500 !important;
        line-height: 1 !important;
        white-space: nowrap !important;
        overflow: hidden !important;
        text-overflow: ellipsis !important;
      }

      @keyframes dzf-status-pulse {
        0%, 100% {
          opacity: 1;
          box-shadow:
            0 0 0 0 rgba(34, 197, 94, 0.50);
        }

        50% {
          opacity: 0.55;
          box-shadow:
            0 0 0 5px rgba(34, 197, 94, 0);
        }
      }
    `;

    (
      document.head ||
      document.documentElement
    ).appendChild(style);
  }

  function mountStatus() {
    const form = document.querySelector(FORM_SELECTOR);

    if (!form?.isConnected) return false;

    injectStyles();

    let status = document.getElementById(STATUS_ID);

    if (!status) {
      status = document.createElement('div');
      status.id = STATUS_ID;
    }

    status.title =
      `DeepZer Flows Compact Sender v${VERSION} activo`;

    status.innerHTML = `
      <span
        class="dzf-status-dot"
        aria-hidden="true"
      ></span>
      <span>En uso · Flows TEMP v${VERSION}</span>
    `;

    if (status.previousElementSibling !== form) {
      form.insertAdjacentElement('afterend', status);
    }

    return true;
  }

  function mountStatusWithRetries(attempt = 0) {
    if (mountStatus() || attempt >= 20) return;

    window.setTimeout(
      () => mountStatusWithRetries(attempt + 1),
      400
    );
  }

  function isVisible(element) {
    if (!(element instanceof Element)) return false;

    const rect = element.getBoundingClientRect();
    const style = getComputedStyle(element);

    return (
      rect.width > 0 &&
      rect.height > 0 &&
      style.display !== 'none' &&
      style.visibility !== 'hidden'
    );
  }

  function messageMatches(text, originalMessage) {
    const candidate = normalizeText(text);
    const original = normalizeText(originalMessage);

    if (!candidate || !original) return false;

    if (candidate === original) return true;

    const prefix = original.slice(
      0,
      Math.min(100, original.length)
    );

    const suffix = original.slice(
      Math.max(0, original.length - 60)
    );

    return (
      candidate.includes(prefix) &&
      (
        original.length < 120 ||
        !suffix ||
        candidate.includes(suffix)
      )
    );
  }

  function findNewestUserMessage(originalMessage) {
    const containers = Array.from(
      document.querySelectorAll('div[data-message-id]')
    ).filter((container) => {
      if (!isVisible(container)) return false;

      const userGroup =
        container.querySelector('.group\\/user-message');

      if (!userGroup) return false;

      return messageMatches(
        container.textContent,
        originalMessage
      );
    });

    if (!containers.length) return null;

    return containers.reduce((latest, current) => {
      if (!latest) return current;

      const latestRect =
        latest.getBoundingClientRect();

      const currentRect =
        current.getBoundingClientRect();

      return currentRect.bottom >= latestRect.bottom
        ? current
        : latest;
    }, null);
  }

  function findBubbleInside(container, originalMessage) {
    if (!(container instanceof Element)) return null;

    const nativeChip =
      container.querySelector('div.special-message');

    if (nativeChip) return nativeChip;

    const preferred = Array.from(
      container.querySelectorAll(
        'div[class*="bg-secondary-pulse"], ' +
        'div[class*="whitespace-pre-wrap"], ' +
        'div[class*="rounded-br-1"], ' +
        'div[class*="max-w-[300px]"]'
      )
    ).filter((element) => {
      if (!isVisible(element)) return false;
      if (element.closest(FORM_SELECTOR)) return false;

      return messageMatches(
        element.textContent,
        originalMessage
      );
    });

    if (preferred.length) {
      return preferred.reduce((best, current) => {
        if (!best) return current;

        return current.contains(best)
          ? current
          : best;
      }, null);
    }

    const all = Array.from(
      container.querySelectorAll('div, p, span')
    )
      .filter((element) => {
        if (!isVisible(element)) return false;
        if (element.closest(FORM_SELECTOR)) return false;

        return messageMatches(
          element.textContent,
          originalMessage
        );
      })
      .sort(
        (a, b) =>
          b.querySelectorAll('*').length -
          a.querySelectorAll('*').length
      );

    return all[0] || null;
  }

  function convertToFixChip(target) {
    if (!(target instanceof Element)) return false;

    const chip =
      target.matches('div.special-message')
        ? target
        : (() => {
            target.replaceChildren();

            const element =
              document.createElement('div');

            element.className =
              'special-message dzf-fix-chip';

            target.appendChild(element);

            return element;
          })();

    chip.textContent = CHIP_TEXT;
    chip.classList.add('dzf-fix-chip');
    chip.dataset.dzfCompact = VERSION;
    chip.setAttribute('aria-label', CHIP_TEXT);
    chip.title = CHIP_TEXT;

    return true;
  }

  function compactOutgoingMessage(originalMessage) {
    injectStyles();

    const startedAt = Date.now();

    let observer = null;
    let stopped = false;

    const stop = () => {
      if (stopped) return;

      stopped = true;
      observer?.disconnect();
    };

    const inspect = () => {
      if (stopped) return true;

      const container =
        findNewestUserMessage(originalMessage);

      if (container) {
        const bubble =
          findBubbleInside(
            container,
            originalMessage
          );

        if (
          bubble &&
          convertToFixChip(bubble)
        ) {
          log(
            'Burbuja de DeepZer Flows convertida a Fix error.'
          );

          stop();
          return true;
        }
      }

      if (
        Date.now() - startedAt >=
        VISUAL_TIMEOUT_MS
      ) {
        warn(
          'No se encontró la nueva burbuja dentro del tiempo de búsqueda.'
        );

        stop();
      }

      return false;
    };

    const begin = () => {
      if (
        stopped ||
        !document.body
      ) {
        return;
      }

      observer = new MutationObserver(() => {
        inspect();
      });

      observer.observe(
        document.body,
        {
          childList: true,
          subtree: true,
          characterData: true
        }
      );

      [
        0,
        60,
        120,
        240,
        450,
        800,
        1300,
        2100,
        3500,
        5500,
        8000,
        11000
      ].forEach((delay) => {
        window.setTimeout(inspect, delay);
      });

      window.setTimeout(
        stop,
        VISUAL_TIMEOUT_MS + 300
      );
    };

    if (document.body) {
      begin();
    } else {
      document.addEventListener(
        'DOMContentLoaded',
        begin,
        {
          once: true
        }
      );
    }
  }

  function installFetchInterceptor() {
    const currentFetch = window.fetch;

    if (typeof currentFetch !== 'function') {
      return false;
    }

    if (
      currentFetch[FETCH_MARKER] === VERSION
    ) {
      return true;
    }

    async function dzfFetch(input, init) {
      if (!isChatRequest(input, init)) {
        return currentFetch.call(
          window,
          input,
          init
        );
      }

      try {
        const modified =
          modifyStringBody(init?.body);

        if (modified) {
          /*
           * La búsqueda visual empieza antes de
           * entregar la solicitud a Lovable.
           */
          compactOutgoingMessage(
            modified.originalMessage
          );

          return currentFetch.call(
            window,
            input,
            {
              ...init,
              body: modified.body
            }
          );
        }

        if (input instanceof Request) {
          const rawBody =
            await input.clone().text();

          const requestModified =
            modifyStringBody(rawBody);

          if (requestModified) {
            compactOutgoingMessage(
              requestModified.originalMessage
            );

            const nextRequest =
              new Request(input, {
                body: requestModified.body
              });

            return currentFetch.call(
              window,
              nextRequest,
              init
            );
          }
        }
      } catch (error) {
        warn(
          'Se mantuvo el envío original por seguridad.',
          error
        );
      }

      return currentFetch.call(
        window,
        input,
        init
      );
    }

    Object.defineProperty(
      dzfFetch,
      FETCH_MARKER,
      {
        value: VERSION,
        configurable: false,
        enumerable: false,
        writable: false
      }
    );

    window.fetch = dzfFetch;

    log(
      'Interceptor temporal instalado para DeepZer Flows.'
    );

    return (
      window.fetch[FETCH_MARKER] === VERSION
    );
  }

  installFetchInterceptor();

  window.setTimeout(
    installFetchInterceptor,
    1200
  );

  window.setTimeout(
    installFetchInterceptor,
    3500
  );

  if (
    document.readyState === 'loading'
  ) {
    document.addEventListener(
      'DOMContentLoaded',
      mountStatusWithRetries,
      {
        once: true
      }
    );
  } else {
    mountStatusWithRetries();
  }

  document.addEventListener(
    'focusin',
    (event) => {
      if (
        event.target?.closest?.(
          FORM_SELECTOR
        )
      ) {
        mountStatus();
      }
    },
    true
  );
})();