refactor: enhance chat components with type safety and response functionality
- Updated type definitions in ChatWindow and MessageList components for better type safety. - Improved MessageInput to handle message responses, including a preview feature for replying to messages. - Enhanced the chat message handling logic to support message references and improve user interaction. - Refactored notification utility functions to support push notifications and rate limiting for email sending. - Updated backend schema to accommodate new features related to message responses and notifications.
This commit is contained in:
138
packages/backend/convex/actions/linkPreview.ts
Normal file
138
packages/backend/convex/actions/linkPreview.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
"use node";
|
||||
|
||||
import { action } from "../_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import { internal } from "../_generated/api";
|
||||
|
||||
/**
|
||||
* Extrair preview de link (metadados Open Graph) - função auxiliar
|
||||
*/
|
||||
async function extrairPreviewLinkHelper(url: string) {
|
||||
try {
|
||||
// Validar URL
|
||||
let urlObj: URL;
|
||||
try {
|
||||
urlObj = new URL(url);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Buscar HTML da página
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (compatible; SGSE-Bot/1.0)",
|
||||
},
|
||||
signal: AbortSignal.timeout(5000), // Timeout de 5 segundos
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const html = await response.text();
|
||||
|
||||
// Extrair metadados Open Graph e Twitter Cards
|
||||
const metadata: {
|
||||
titulo?: string;
|
||||
descricao?: string;
|
||||
imagem?: string;
|
||||
site?: string;
|
||||
} = {};
|
||||
|
||||
// Título (og:title ou twitter:title ou <title>)
|
||||
const ogTitleMatch = html.match(/<meta\s+property=["']og:title["']\s+content=["']([^"']+)["']/i);
|
||||
const twitterTitleMatch = html.match(/<meta\s+name=["']twitter:title["']\s+content=["']([^"']+)["']/i);
|
||||
const titleMatch = html.match(/<title>([^<]+)<\/title>/i);
|
||||
|
||||
metadata.titulo = ogTitleMatch?.[1] || twitterTitleMatch?.[1] || titleMatch?.[1] || undefined;
|
||||
if (metadata.titulo) {
|
||||
metadata.titulo = metadata.titulo.trim().substring(0, 200);
|
||||
}
|
||||
|
||||
// Descrição (og:description ou twitter:description ou meta description)
|
||||
const ogDescMatch = html.match(/<meta\s+property=["']og:description["']\s+content=["']([^"']+)["']/i);
|
||||
const twitterDescMatch = html.match(/<meta\s+name=["']twitter:description["']\s+content=["']([^"']+)["']/i);
|
||||
const metaDescMatch = html.match(/<meta\s+name=["']description["']\s+content=["']([^"']+)["']/i);
|
||||
|
||||
metadata.descricao = ogDescMatch?.[1] || twitterDescMatch?.[1] || metaDescMatch?.[1] || undefined;
|
||||
if (metadata.descricao) {
|
||||
metadata.descricao = metadata.descricao.trim().substring(0, 300);
|
||||
}
|
||||
|
||||
// Imagem (og:image ou twitter:image)
|
||||
const ogImageMatch = html.match(/<meta\s+property=["']og:image["']\s+content=["']([^"']+)["']/i);
|
||||
const twitterImageMatch = html.match(/<meta\s+name=["']twitter:image["']\s+content=["']([^"']+)["']/i);
|
||||
|
||||
const imageUrl = ogImageMatch?.[1] || twitterImageMatch?.[1];
|
||||
if (imageUrl) {
|
||||
// Resolver URL relativa
|
||||
try {
|
||||
metadata.imagem = new URL(imageUrl, url).href;
|
||||
} catch {
|
||||
metadata.imagem = imageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// Site (og:site_name ou domínio)
|
||||
const ogSiteMatch = html.match(/<meta\s+property=["']og:site_name["']\s+content=["']([^"']+)["']/i);
|
||||
metadata.site = ogSiteMatch?.[1] || urlObj.hostname.replace(/^www\./, "");
|
||||
|
||||
return {
|
||||
url,
|
||||
titulo: metadata.titulo,
|
||||
descricao: metadata.descricao,
|
||||
imagem: metadata.imagem,
|
||||
site: metadata.site,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Erro ao extrair preview de link:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processar preview de link e atualizar mensagem
|
||||
*/
|
||||
export const processarPreviewLink = action({
|
||||
args: {
|
||||
mensagemId: v.id("mensagens"),
|
||||
url: v.string(),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
// Extrair preview
|
||||
const preview = await extrairPreviewLinkHelper(args.url);
|
||||
|
||||
if (preview) {
|
||||
// Atualizar mensagem com preview
|
||||
await ctx.runMutation(internal.chat.atualizarLinkPreview, {
|
||||
mensagemId: args.mensagemId,
|
||||
linkPreview: preview,
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Extrair preview de link (metadados Open Graph) - versão pública
|
||||
*/
|
||||
export const extrairPreviewLink = action({
|
||||
args: {
|
||||
url: v.string(),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({
|
||||
url: v.string(),
|
||||
titulo: v.optional(v.string()),
|
||||
descricao: v.optional(v.string()),
|
||||
imagem: v.optional(v.string()),
|
||||
site: v.optional(v.string()),
|
||||
}),
|
||||
v.null()
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
return await extrairPreviewLinkHelper(args.url);
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user