DeepZer Flows Compact Sender TEMP

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

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

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

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

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

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

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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
  );
})();