paint-brush
Como construir um aplicativo de resumo de páginas da Web com Next.js, OpenAI, LangChain e Supabasepor@nassermaronie
Novo histórico

Como construir um aplicativo de resumo de páginas da Web com Next.js, OpenAI, LangChain e Supabase

por Nasser Maronie13m2024/06/27
Read on Terminal Reader

Muito longo; Para ler

Neste artigo, mostraremos como criar um aplicativo web útil que pode resumir o conteúdo de qualquer página web. Usando [Next.js] para uma experiência web tranquila e rápida, [LangChain] para linguagem de processamento, [OpenAI](https://openai.com/) para gerar resumos e [Supabase] para gerenciar e armazenar dados vetoriais, nós construiremos juntos uma ferramenta poderosa.
featured image - Como construir um aplicativo de resumo de páginas da Web com Next.js, OpenAI, LangChain e Supabase
Nasser Maronie HackerNoon profile picture

Um aplicativo que pode compreender o contexto de qualquer página da web.

Neste artigo, mostraremos como criar um aplicativo web útil que pode resumir o conteúdo de qualquer página web. Usando Next.js para uma experiência web rápida e tranquila, LangChain para linguagem de processamento, OpenAI para gerar resumos e Supabase para gerenciar e armazenar dados vetoriais, construiremos juntos uma ferramenta poderosa.



Por que estamos construindo isso

Todos enfrentamos uma sobrecarga de informação com tanto conteúdo online. Ao criar um aplicativo que fornece resumos rápidos, ajudamos as pessoas a economizar tempo e a se manterem informadas. Quer você seja um trabalhador ocupado, um estudante ou apenas alguém que deseja ficar por dentro de notícias e artigos, este aplicativo será uma ferramenta útil para você.

Como vai ser

Nosso aplicativo permitirá que os usuários insiram qualquer URL de site e obtenham rapidamente um breve resumo da página. Isso significa que você pode entender os pontos principais de artigos longos, postagens de blogs ou trabalhos de pesquisa sem lê-los completamente.

Potencial e Impacto

Este aplicativo de resumo pode ser útil de várias maneiras. Ele pode ajudar os pesquisadores a folhear trabalhos acadêmicos, manter os amantes de notícias atualizados e muito mais. Além disso, os desenvolvedores podem aproveitar este aplicativo para criar recursos ainda mais úteis.


Pilhas de tecnologia

Próximo.js

Next.js é uma estrutura React poderosa e flexível desenvolvida pela Vercel que permite aos desenvolvedores construir renderização do lado do servidor (SSR) e aplicativos da web estáticos com facilidade. Ele combina os melhores recursos do React com recursos adicionais para criar aplicativos web otimizados e escalonáveis.

OpenAI

O módulo OpenAI em Node.js fornece uma maneira de interagir com a API OpenAI, permitindo que os desenvolvedores aproveitem modelos de linguagem poderosos como GPT-3 e GPT-4. Este módulo permite integrar funcionalidades avançadas de IA em seus aplicativos Node.js.

LangChain.js

LangChain é um framework poderoso projetado para desenvolver aplicações com modelos de linguagem. Originalmente desenvolvido para Python, desde então foi adaptado para outras linguagens, incluindo Node.js. Aqui está uma visão geral do LangChain no contexto do Node.js:

O que é LangChain?

LangChain é uma biblioteca que simplifica a criação de aplicações usando grandes modelos de linguagem (LLMs) . Ele fornece ferramentas para gerenciar e integrar LLMs em seus aplicativos, lidar com o encadeamento de chamadas para esses modelos e permitir fluxos de trabalho complexos com facilidade.

Como funcionam os modelos de linguagem grande (LLM)?

Grandes modelos de linguagem (LLMs), como o GPT-3.5 da OpenAI, são treinados em grandes quantidades de dados de texto para compreender e gerar texto semelhante ao humano. Eles podem gerar respostas, traduzir idiomas e realizar muitas outras tarefas de processamento de linguagem natural.

Supabase

Supabase é uma plataforma de backend como serviço (BaaS) de código aberto projetada para ajudar os desenvolvedores a construir e implantar rapidamente aplicativos escalonáveis. Ele oferece um conjunto de ferramentas e serviços que simplificam o gerenciamento de banco de dados, autenticação, armazenamento e recursos em tempo real, tudo construído sobre PostgreSQL


Pré-requisitos

Antes de começarmos, certifique-se de ter o seguinte:

  • Node.js e npm instalados
  • Uma conta Supabase
  • Uma conta OpenAI

Etapa 1: Configurando o Supabase

Primeiro, precisamos montar um projeto Supabase e criar as tabelas necessárias para armazenar nossos dados.

Crie um projeto Supabase

  1. Vá para Supabase e crie uma conta.


  2. Crie um novo projeto e anote seu URL Supabase e chave API. Você precisará disso mais tarde.

Script SQL para Supabase

Crie uma nova consulta SQL em seu painel Supabase e execute os seguintes scripts para criar as tabelas e funções necessárias:

Primeiro, crie uma extensão se ela ainda não existir para nosso armazenamento de vetores:

 create extension if not exists vector;


A seguir, crie uma tabela chamada “documentos”. Esta tabela será usada para armazenar e incorporar o conteúdo da página web em formato vetorial:

 create table if not exists documents ( id bigint primary key generated always as identity, content text, metadata jsonb, embedding vector(1536) );


Agora, precisamos de uma função para consultar nossos dados incorporados:

 create or replace function match_documents ( query_embedding vector(1536), match_count int default null, filter jsonb default '{}' ) returns table ( id bigint, content text, metadata jsonb, similarity float ) language plpgsql as $$ begin return query select id, content, metadata, 1 - (documents.embedding <=> query_embedding) as similarity from documents where metadata @> filter order by documents.embedding <=> query_embedding limit match_count; end; $$;


A seguir, precisamos configurar nossa tabela para armazenar os detalhes da página web:

 create table if not exists files ( id bigint primary key generated always as identity, url text not null, created_at timestamp with time zone default timezone('utc'::text, now()) not null );

Etapa 2: Configurando o OpenAI

Criar projeto OpenAI


  • Navegue até API: Após fazer login, navegue até a seção API e crie uma nova chave de API. Isso geralmente é acessível no painel.

Etapa 3: configurar Next.js

Criar aplicativo Next.js

 $ npx create-next-app summarize-page $ cd ./summarize-page


Instale as dependências necessárias:

 npm install @langchain/community @langchain/core @langchain/openai @supabase/supabase-js langchain openai axios


Em seguida, instalaremos o Material UI para construir nossa interface; sinta-se à vontade para usar outra biblioteca:

 npm install @mui/material @emotion/react @emotion/styled

Etapa 4: Clientes OpenAI e Supabase

A seguir, precisamos configurar os clientes OpenAI e Supabase. Crie um diretório libs em seu projeto e adicione os seguintes arquivos.

src/libs/openAI.ts

Este arquivo irá configurar o cliente OpenAI.

 import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai"; const openAIApiKey = process.env.OPENAI_API_KEY; if (!openAIApiKey) throw new Error('OpenAI API Key not found.') export const llm = new ChatOpenAI({ openAIApiKey, modelName: "gpt-3.5-turbo", temperature: 0.9, }); export const embeddings = new OpenAIEmbeddings( { openAIApiKey, }, { maxRetries: 0 } );
  • llm : A instância do modelo de linguagem, que irá gerar nossos resumos.


  • embeddings : Isso criará embeddings para nossos documentos, que ajudam a encontrar conteúdo semelhante.

src/libs/supabaseClient.ts

Este arquivo irá configurar o cliente Supabase.

 import { createClient } from "@supabase/supabase-js"; const supabaseUrl = process.env.SUPABASE_URL || ""; const supabaseAnonKey = process.env.SUPABASE_ANON_KEY || ""; if (!supabaseUrl) throw new Error("Supabase URL not found."); if (!supabaseAnonKey) throw new Error("Supabase Anon key not found."); export const supabaseClient = createClient(supabaseUrl, supabaseAnonKey);
  • supabaseClient : A instância do cliente Supabase para interagir com nosso banco de dados Supabase.

Etapa 5: Criação de serviços para conteúdo e arquivos

Crie um diretório services e adicione os arquivos a seguir para lidar com a busca de conteúdo e o gerenciamento de arquivos.

src/services/content.ts

Este serviço irá buscar o conteúdo da página da web e limpá-lo, removendo tags, scripts e estilos HTML.

 import axios from "axios"; export async function getContent(url: string): Promise<string> { let htmlContent: string = ""; const response = await axios.get(url as string); htmlContent = response.data; if (!htmlContent) return ""; // Remove unwanted elements and tags return htmlContent .replace(/style="[^"]*"/gi, "") .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "") .replace(/\s*on\w+="[^"]*"/gi, "") .replace( /<script(?![^>]*application\/ld\+json)[^>]*>[\s\S]*?<\/script>/gi, "" ) .replace(/<[^>]*>/g, "") .replace(/\s+/g, " "); }

Esta função busca o conteúdo HTML de um determinado URL e o limpa removendo estilos, scripts e tags HTML.

src/services/file.ts

Este serviço salvará o conteúdo da página web no Supabase e recuperará resumos.

 import { embeddings, llm } from "@/libs/openAI"; import { supabaseClient } from "@/libs/supabaseClient"; import { SupabaseVectorStore } from "@langchain/community/vectorstores/supabase"; import { StringOutputParser } from "@langchain/core/output_parsers"; import { ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate, } from "@langchain/core/prompts"; import { RunnablePassthrough, RunnableSequence, } from "@langchain/core/runnables"; import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"; import { formatDocumentsAsString } from "langchain/util/document"; export interface IFile { id?: number | undefined; url: string; created_at?: Date | undefined; } export async function saveFile(url: string, content: string): Promise<IFile> { const doc = await supabaseClient .from("files") .select() .eq("url", url) .single<IFile>(); if (!doc.error && doc.data?.id) return doc.data; const { data, error } = await supabaseClient .from("files") .insert({ url }) .select() .single<IFile>(); if (error) throw error; const splitter = new RecursiveCharacterTextSplitter({ separators: ["\n\n", "\n", " ", ""], }); const output = await splitter.createDocuments([content]); const docs = output.map((d) => ({ ...d, metadata: { ...d.metadata, file_id: data.id }, })); await SupabaseVectorStore.fromDocuments(docs, embeddings, { client: supabaseClient, tableName: "documents", queryName: "match_documents", }); return data; } export async function getSummarization(fileId: number): Promise<string> { const vectorStore = await SupabaseVectorStore.fromExistingIndex(embeddings, { client: supabaseClient, tableName: "documents", queryName: "match_documents", }); const retriever = vectorStore.asRetriever({ filter: (rpc) => rpc.filter("metadata->>file_id", "eq", fileId), k: 2, }); const SYSTEM_TEMPLATE = `Use the following pieces of context, explain what is it about and summarize it. If you can't explain it, just say that you don't know, don't try to make up some explanation. ---------------- {context}`; const messages = [ SystemMessagePromptTemplate.fromTemplate(SYSTEM_TEMPLATE), HumanMessagePromptTemplate.fromTemplate("{format_answer}"), ]; const prompt = ChatPromptTemplate.fromMessages(messages); const chain = RunnableSequence.from([ { context: retriever.pipe(formatDocumentsAsString), format_answer: new RunnablePassthrough(), }, prompt, llm, new StringOutputParser(), ]); const format_summarization = ` Give it title, subject, description, and the conclusion of the context in this format, replace the brackets with the actual content: [Write the title here] By: [Name of the author or owner or user or publisher or writer or reporter if possible, otherwise leave it "Not Specified"] [Write the subject, it could be a long text, at least minimum of 300 characters] ---------------- [Write the description in here, it could be a long text, at least minimum of 1000 characters] Conclusion: [Write the conclusion in here, it could be a long text, at least minimum of 500 characters] `; const summarization = await chain.invoke(format_summarization); return summarization; }
  • saveFile : salva o arquivo e seu conteúdo no Supabase, divide o conteúdo em partes gerenciáveis e os armazena no armazenamento de vetores.


  • getSummarization : recupera documentos relevantes do armazenamento de vetores e gera um resumo usando OpenAI.

Etapa 6: Criando um manipulador de API

Agora, vamos criar um manipulador de API para processar o conteúdo e gerar um resumo.

pages/api/content.ts

 import { getContent } from "@/services/content"; import { getSummarization, saveFile } from "@/services/file"; import { NextApiRequest, NextApiResponse } from "next"; export default async function handler( req: NextApiRequest, res: NextApiResponse ) { if (req.method !== "POST") return res.status(404).json({ message: "Not found" }); const { body } = req; try { const content = await getContent(body.url); const file = await saveFile(body.url, content); const result = await getSummarization(file.id as number); res.status(200).json({ result }); } catch (err) { res.status( 500).json({ error: err }); } }

Este manipulador de API recebe uma URL, busca o conteúdo, salva-o no Supabase e gera um resumo. Ele lida com as funções saveFile e getSummarization de nossos serviços.


Etapa 7: Construindo o Frontend

Finalmente, vamos criar o frontend em src/pages/index.tsx para permitir que os usuários insiram URLs e exibam os resumos.

src/pages/index.tsx

 import axios from "axios"; import { useState } from "react"; import { Alert, Box, Button, Container, LinearProgress, Stack, TextField, Typography, } from "@mui/material"; export default function Home() { const [loading, setLoading] = useState(false); const [url, setUrl] = useState(""); const [result, setResult] = useState(""); const [error, setError] = useState<any>(null); const onSubmit = async () => { try { setError(null); setLoading(true); const res = await axios.post("/api/content", { url }); setResult(res.data.result); } catch (err) { console.error("Failed to fetch content", err); setError(err as any); } finally { setLoading(false); } }; return ( <Box sx={{ height: "100vh", overflowY: "auto" }}> <Container sx={{ backgroundColor: (theme) => theme.palette.background.default, position: "sticky", top: 0, zIndex: 2, py: 2, }} > <Typography sx={{ mb: 2, fontSize: "24px" }}> Summarize the content of any page </Typography> <TextField fullWidth label="Input page's URL" value={url} onChange={(e) => { if (result) setResult(""); setUrl(e.target.value); }} sx={{ mb: 2 }} /> <Button disabled={loading} variant="contained" onClick={onSubmit} > Summarize </Button> </Container> <Container maxWidth="lg" sx={{ py: 2 }}> {loading ? ( <LinearProgress /> ) : ( <Stack sx={{ gap: 2 }}> {result && ( <Alert> <Typography sx={{ whiteSpace: "pre-line", wordBreak: "break-word", }} > {result} </Typography> </Alert> )} {error && <Alert severity="error">{error.message || error}</Alert>} </Stack> )} </Container> </Box> ); }

Este componente React permite aos usuários inserir uma URL, enviá-la e exibir o resumo gerado. Ele lida com estados de carregamento e mensagens de erro para fornecer uma melhor experiência ao usuário.


Etapa 8: executando o aplicativo

Crie um arquivo .env na raiz do seu projeto para armazenar suas variáveis de ambiente:

 SUPABASE_URL=your-supabase-url SUPABASE_ANON_KEY=your-supabase-anon-key OPENAI_API_KEY=your-openai-api-key


Finalmente, inicie seu aplicativo Next.js:

 npm run dev


Agora, você deve ter um aplicativo em execução onde poderá inserir o URL da página da web e receber as respostas resumidas da página.


Conclusão

Parabéns! Você construiu um aplicativo de resumo de páginas da web totalmente funcional usando Next.js, OpenAI, LangChain e Supabase. Os usuários podem inserir uma URL, buscar o conteúdo, armazená-lo no Supabase e gerar um resumo usando os recursos do OpenAI. Esta configuração fornece uma base robusta para melhorias adicionais e personalização com base nas suas necessidades.


Sinta-se à vontade para expandir este projeto adicionando mais recursos, melhorando a IU ou integrando APIs adicionais.

Verifique o código-fonte neste repositório:

https://github.com/firstpersoncode/summarize-page


Boa codificação!