Dans cet article, nous allons vous montrer comment créer une application Web pratique capable de résumer le contenu de n'importe quelle page Web. En utilisant Next.js pour une expérience Web fluide et rapide, LangChain pour le langage de traitement, OpenAI pour générer des résumés et Supabase pour gérer et stocker des données vectorielles, nous créerons ensemble un outil puissant.
Nous sommes tous confrontés à une surcharge d’informations avec autant de contenu en ligne. En créant une application qui donne des résumés rapides, nous aidons les gens à gagner du temps et à rester informés. Que vous soyez un travailleur occupé, un étudiant ou simplement quelqu'un qui souhaite se tenir au courant des actualités et des articles, cette application sera un outil utile pour vous.
Notre application permettra aux utilisateurs de saisir l'URL de n'importe quel site Web et d'obtenir rapidement un bref résumé de la page. Cela signifie que vous pouvez comprendre les points principaux de longs articles, de blogs ou de documents de recherche sans les lire entièrement.
Cette application de synthèse peut être utile de plusieurs manières. Il peut aider les chercheurs à parcourir des articles universitaires, à tenir les amateurs d'actualités informés, et bien plus encore. De plus, les développeurs peuvent s'appuyer sur cette application pour créer des fonctionnalités encore plus utiles.
Next.js est un framework React puissant et flexible développé par Vercel qui permet aux développeurs de créer facilement des applications Web statiques et de rendu côté serveur (SSR). Il combine les meilleures fonctionnalités de React avec des fonctionnalités supplémentaires pour créer des applications Web optimisées et évolutives.
Le module OpenAI de Node.js fournit un moyen d'interagir avec l'API d'OpenAI, permettant aux développeurs d'exploiter des modèles de langage puissants tels que GPT-3 et GPT-4. Ce module vous permet d'intégrer des fonctionnalités avancées d'IA dans vos applications Node.js.
LangChain est un framework puissant conçu pour développer des applications avec des modèles de langage. Développé à l’origine pour Python, il a depuis été adapté pour d’autres langages, dont Node.js. Voici un aperçu de LangChain dans le contexte de Node.js :
LangChain est une bibliothèque qui simplifie la création d'applications utilisant de grands modèles de langage (LLM) . Il fournit des outils pour gérer et intégrer les LLM dans vos applications, gérer l'enchaînement des appels à ces modèles et activer facilement des flux de travail complexes.
Les grands modèles linguistiques (LLM) comme le GPT-3.5 d'OpenAI sont formés sur de grandes quantités de données textuelles pour comprendre et générer du texte de type humain. Ils peuvent générer des réponses, traduire des langues et effectuer de nombreuses autres tâches de traitement du langage naturel.
Supabase est une plate-forme open source backend-as-a-service (BaaS) conçue pour aider les développeurs à créer et déployer rapidement des applications évolutives. Il offre une suite d'outils et de services qui simplifient la gestion des bases de données, l'authentification, le stockage et les capacités en temps réel, le tout basé sur PostgreSQL.
Avant de commencer, assurez-vous d'avoir les éléments suivants :
Tout d’abord, nous devons mettre en place un projet Supabase et créer les tables nécessaires pour stocker nos données.
Accédez à Supabase et créez un compte.
Créez un nouveau projet et notez votre URL Supabase et votre clé API. Vous en aurez besoin plus tard.
Créez une nouvelle requête SQL dans votre tableau de bord Supabase et exécutez les scripts suivants pour créer les tables et fonctions requises :
Tout d'abord, créez une extension si elle n'existe pas déjà pour notre magasin de vecteurs :
create extension if not exists vector;
Ensuite, créez un tableau nommé « documents ». Ce tableau servira à stocker et embarquer le contenu de la page web au format vectoriel :
create table if not exists documents ( id bigint primary key generated always as identity, content text, metadata jsonb, embedding vector(1536) );
Maintenant, nous avons besoin d'une fonction pour interroger nos données intégrées :
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; $$;
Ensuite, nous devons configurer notre table pour stocker les détails de la page 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 );
$ npx create-next-app summarize-page $ cd ./summarize-page
Installez les dépendances requises :
npm install @langchain/community @langchain/core @langchain/openai @supabase/supabase-js langchain openai axios
Ensuite, nous installerons Material UI pour construire notre interface ; n'hésitez pas à utiliser une autre bibliothèque :
npm install @mui/material @emotion/react @emotion/styled
Ensuite, nous devons configurer les clients OpenAI et Supabase. Créez un répertoire libs
dans votre projet et ajoutez les fichiers suivants.
src/libs/openAI.ts
Ce fichier configurera le client 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
: L'instance du modèle de langage, qui générera nos résumés.
embeddings
: cela créera des intégrations pour nos documents, ce qui aidera à trouver un contenu similaire.src/libs/supabaseClient.ts
Ce fichier configurera le client 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
: L'instance client Supabase pour interagir avec notre base de données Supabase. Créez un répertoire services
et ajoutez les fichiers suivants pour gérer la récupération du contenu et la gestion des fichiers.
src/services/content.ts
Ce service récupérera le contenu de la page Web et le nettoiera en supprimant les balises HTML, les scripts et les styles.
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, " "); }
Cette fonction récupère le contenu HTML d'une URL donnée et le nettoie en supprimant les styles, les scripts et les balises HTML.
src/services/file.ts
Ce service enregistrera le contenu de la page Web dans Supabase et récupérera les résumés.
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
: enregistre le fichier et son contenu dans Supabase, divise le contenu en morceaux gérables et les stocke dans le magasin de vecteurs.
getSummarization
: récupère les documents pertinents du magasin de vecteurs et génère un résumé à l'aide d'OpenAI.Créons maintenant un gestionnaire d'API pour traiter le contenu et générer un résumé.
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 }); } }
Ce gestionnaire d'API reçoit une URL, récupère le contenu, l'enregistre dans Supabase et génère un résumé. Il gère à la fois les fonctions saveFile
et getSummarization
de nos services.
Enfin, créons le frontend dans src/pages/index.tsx
pour permettre aux utilisateurs de saisir des URL et d'afficher les résumés.
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> ); }
Ce composant React permet aux utilisateurs de saisir une URL, de la soumettre et d'afficher le résumé généré. Il gère les états de chargement et les messages d'erreur pour offrir une meilleure expérience utilisateur.
Créez un fichier .env à la racine de votre projet pour stocker vos variables d'environnement :
SUPABASE_URL=your-supabase-url SUPABASE_ANON_KEY=your-supabase-anon-key OPENAI_API_KEY=your-openai-api-key
Enfin, démarrez votre application Next.js :
npm run dev
Vous devriez maintenant disposer d'une application en cours d'exécution dans laquelle vous pouvez saisir l'URL de la page Web et recevoir les réponses résumées de la page.
Toutes nos félicitations! Vous avez créé une application de synthèse de pages Web entièrement fonctionnelle à l'aide de Next.js, OpenAI, LangChain et Supabase. Les utilisateurs peuvent saisir une URL, récupérer le contenu, le stocker dans Supabase et générer un résumé à l'aide des capacités d'OpenAI. Cette configuration fournit une base solide pour des améliorations et une personnalisation supplémentaires en fonction de vos besoins.
N'hésitez pas à développer ce projet en ajoutant plus de fonctionnalités, en améliorant l'interface utilisateur ou en intégrant des API supplémentaires.
https://github.com/firstpersoncode/summarize-page
Bon codage !