From f8c9acae0ef1e3b160f33332c9ee3a1e1248b34e Mon Sep 17 00:00:00 2001 From: oswaldo martinez Date: Mon, 30 Jun 2025 12:24:03 -0500 Subject: [PATCH] ready to share your asset link with an aq message description --- app/routes/api/v1/ai/sugestions.tsx | 21 ++++ app/routes/assets/AssetPreview.tsx | 175 +++++++++++++++++++++++++--- 2 files changed, 182 insertions(+), 14 deletions(-) diff --git a/app/routes/api/v1/ai/sugestions.tsx b/app/routes/api/v1/ai/sugestions.tsx index 428728a9..fb70a5ff 100644 --- a/app/routes/api/v1/ai/sugestions.tsx +++ b/app/routes/api/v1/ai/sugestions.tsx @@ -28,5 +28,26 @@ export const action = async ({ request }: { request: Request }) => { }); } } + // i want a new action where I return a description to share in social media based on the name, type and info from the asset i want to share/sell that is well structured based on the platfor it will be shareD + if (intent === "generate_social_description") { + const prompt = buildPromptFromHistory(chat); + const ollamaResponse = await fetchInternalOllama(prompt, true); + console.info("OLLAMA_RESPONSE::\n", ollamaResponse.status); + if (ollamaResponse.status > 399) { + const d = await ollamaResponse.json(); + console.error("OLLAMA_ERROR::\n", d); + } + + if (ollamaResponse.ok) { + return new Response(ollamaResponse.body, { + headers: { + "Content-Type": "text/plain", + "Cache-Control": "no-cache", + Connection: "keep-alive", + }, + }); + } + } + return null; }; diff --git a/app/routes/assets/AssetPreview.tsx b/app/routes/assets/AssetPreview.tsx index 7ffdd314..463b271a 100644 --- a/app/routes/assets/AssetPreview.tsx +++ b/app/routes/assets/AssetPreview.tsx @@ -17,6 +17,7 @@ import { useAnimate, motion } from "motion/react"; import { SiGmail } from "react-icons/si"; import { ContentTemplate, HeaderTemplate } from "./template"; import { useOpenLink } from "~/hooks/useOpenLink"; +import Spinner from "~/components/common/Spinner"; export const AssetPreview = ({ asset, @@ -72,7 +73,7 @@ export const AssetPreview = ({ onClose={handleClose} isOpen={isOpen} host={host} - slug={asset.slug} + asset={asset} /> ); @@ -82,14 +83,14 @@ const ShareLink = ({ isOpen, onClose, host, - slug, + asset, }: { isOpen: boolean; onClose?: () => void; host: string; - slug: string; + asset: Asset; }) => { - const link = `https://${host}.easybits.cloud/tienda/${slug}`; + const link = `https://${host}.easybits.cloud/tienda/${asset?.slug}`; return ( <> - + ); }; -export const Sharing = ({ link }: { link: string }) => { +export const Sharing = ({ link, asset }: { link: string; asset: Asset }) => { + const [text, setText] = useState("¡Vi este post y me pareció interesante!"); + const [abortController, setAbortController] = + useState(null); + const [isLoading, setIsLoading] = useState(false); + + const handleSharelinkWithAIDescription = async ({ + platform, + }: { + platform: string; + }) => { + if (isLoading && abortController) { + abortController.abort(); + } + setIsLoading(true); + const controller = new AbortController(); + setAbortController(controller); + const chat = [ + { + role: "user", + content: `Quiero compartir el siguiente link: ${link} en ${platform} + Genera directamente, sin saludos, explicaciones ni introducciones, una, solo una descripción atractiva, concisa y con un lenguaje informal y con emojis para redes sociales que resalte las características del asset "${asset.title} usa un tono de acuerdo al nombre y tipo del asset ${asset.type}". + Por ejemplo, si el tipo de asset es un ebook y el nombre es "guia para convertirte en un experto en React", la descripción podría ser seria y concisa como: + "¡Descubre esta guía definitiva para convertirte en un experto en React! 🚀📚 Aprende los secretos de los profesionales y lleva tus habilidades al siguiente nivel. ¡Haz clic aquí para obtenerla ahora! 👉"`, + }, + ]; + + try { + const response = await fetch("/api/v1/ai/sugestions", { + method: "POST", + body: new URLSearchParams({ + intent: "generate_social_description", + chat: JSON.stringify(chat), + }), + signal: controller.signal, + }); + // @todo make this a hook + if (response.ok) { + const reader = response.body?.getReader(); + const decoder = new TextDecoder(); + if (!reader) return; + + let buffer = ""; + setText(""); + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value); + const lines = buffer.split("\n"); + + // Mantén la última línea en el buffer por si está incompleta + buffer = lines.pop() || ""; + + for (const line of lines) { + if (line.trim() && line.startsWith("{")) { + try { + const data = JSON.parse(line); + if (data.response) { + setText((v) => v + data.response); + // Hacer scroll después de cada actualización del contenido + } + } catch (e) { + // Ignora errores de parsing de líneas no válidas + console.warn("Línea JSON no válida:", line); + } + } + } + } + setIsLoading(false); + return text; // Devuelve el texto generado + } + } catch (error) { + console.error("Error al generar la descripción:", error); + } finally { + setAbortController(null); + } + }; + return (
{ - navigator.clipboard.writeText(link); + isLoading={isLoading} + onClick={async () => { + const description = await handleSharelinkWithAIDescription({ + platform: "Any text chat", + }); + navigator.clipboard.writeText(description); + toast.success("Link copiado", { style: { border: "2px solid #000000", @@ -139,35 +223,95 @@ export const Sharing = ({ link }: { link: string }) => { { + const description = handleSharelinkWithAIDescription({ + platform: "Facebook", + }); + if (description) { + const facebookURL = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent( + link + )}"e=${encodeURIComponent(description)}`; + window.open(facebookURL, "_blank"); + } + }} > { + const description = await handleSharelinkWithAIDescription({ + platform: "X (twitter)", + }); + if (description) { + const tweetURL = `https://twitter.com/intent/tweet?text=${encodeURIComponent( + description + )}&url=${encodeURIComponent(link)}`; + window.open(tweetURL, "_blank"); + } + }} > { + const description = await handleSharelinkWithAIDescription({ + platform: "linkedin", + }); + if (description) { + const linkedinURL = `https://www.linkedin.com/sharing/share-offsite?mini=true&url=${encodeURIComponent( + link + )}&title=${encodeURIComponent(description)}`; + window.open(linkedinURL, "_blank"); + } + }} > { + const description = await handleSharelinkWithAIDescription({ + platform: "mail", + }); + if (description) { + const gmailURL = `https://mail.google.com/mail/?view=cm&fs=1&to=&su=${encodeURIComponent( + "¡Te comparto mi link!" + )}&body=${encodeURIComponent(description + " " + link)}`; + window.open(gmailURL, "_blank"); + } + }} > { + const description = await handleSharelinkWithAIDescription({ + platform: "Whatsapp", + }); + if (description) { + const whatsappURL = `https://api.whatsapp.com/send?text=${encodeURIComponent( + description + )} ${encodeURIComponent(link)}`; + window.open(whatsappURL, "_blank"); + } + }} > @@ -181,12 +325,14 @@ export const SocialMedia = ({ name, link, onClick, + isLoading, }: { className?: string; children: ReactNode; name?: string; onClick?: () => void; link?: string; + isLoading?: boolean; }) => { const [scope, animate] = useAnimate(); @@ -201,6 +347,7 @@ export const SocialMedia = ({ return (