Spaces:
Sleeping
Sleeping
Fallou Diagne
commited on
Commit
·
c4f2e45
1
Parent(s):
e93d9f3
add all files
Browse files- app.py +247 -0
- requirements.txt +116 -0
app.py
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
from minio import Minio
|
| 3 |
+
from minio.error import S3Error
|
| 4 |
+
from datetime import timedelta
|
| 5 |
+
from agno.agent import Agent
|
| 6 |
+
from agno.knowledge.pdf import PDFKnowledgeBase, PDFReader
|
| 7 |
+
from agno.vectordb.qdrant import Qdrant
|
| 8 |
+
from agno.storage.redis import RedisStorage
|
| 9 |
+
from agno.memory.v2.db.redis import RedisMemoryDb
|
| 10 |
+
from agno.memory.v2.memory import Memory
|
| 11 |
+
from agno.document.chunking.agentic import AgenticChunking
|
| 12 |
+
from agno.models.openai import OpenAIChat
|
| 13 |
+
from agno.tools.reasoning import ReasoningTools
|
| 14 |
+
import gradio as gr
|
| 15 |
+
import json
|
| 16 |
+
import pandas as pd
|
| 17 |
+
from docx import Document
|
| 18 |
+
import tempfile
|
| 19 |
+
from weasyprint import HTML
|
| 20 |
+
import tempfile
|
| 21 |
+
import markdown as md
|
| 22 |
+
import os
|
| 23 |
+
import tempfile
|
| 24 |
+
from PIL import Image
|
| 25 |
+
import io
|
| 26 |
+
|
| 27 |
+
from dotenv import load_dotenv
|
| 28 |
+
load_dotenv()
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
"""
|
| 32 |
+
# Configuration MinIO
|
| 33 |
+
client = Minio(
|
| 34 |
+
os.getenv("ENDPOINT"),
|
| 35 |
+
access_key=os.getenv("ACCESS_KEY"),
|
| 36 |
+
secret_key=os.getenv("SECRET_KEY"),
|
| 37 |
+
secure=False
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
bucket_name = "iachatbotbocs"
|
| 41 |
+
"""
|
| 42 |
+
# Initialisation Qdrant
|
| 43 |
+
vector_db = Qdrant(
|
| 44 |
+
collection="directives_16J",
|
| 45 |
+
url=os.getenv("QDRANT_URL"),
|
| 46 |
+
api_key=os.getenv("QDRANT_API_KEY")
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
# Initialize Redis storage with default local connection
|
| 50 |
+
storage = RedisStorage(
|
| 51 |
+
prefix="agno_test_directive", # Prefix for Redis keys to namespace the sessions
|
| 52 |
+
host="localhost", # Redis host address
|
| 53 |
+
port=6379, # Redis port number
|
| 54 |
+
)
|
| 55 |
+
# Create memory storage
|
| 56 |
+
memory_db = RedisMemoryDb(
|
| 57 |
+
prefix="agno_test_directive",
|
| 58 |
+
host="localhost",
|
| 59 |
+
port=6379,
|
| 60 |
+
)
|
| 61 |
+
memory = Memory(db=memory_db)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
# Charger tous les PDF du dossier dans la base de connaissance
|
| 65 |
+
knowledge_base = PDFKnowledgeBase(
|
| 66 |
+
#path="/home/fallou/PROJECT/Repo-IA/AGNO/directive",
|
| 67 |
+
reader=PDFReader(chunk=True),
|
| 68 |
+
vector_db=vector_db,
|
| 69 |
+
chunking_strategy=AgenticChunking(),
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
agent = Agent(
|
| 73 |
+
model=OpenAIChat(id="gpt-4o"),
|
| 74 |
+
knowledge=knowledge_base,
|
| 75 |
+
search_knowledge=True,
|
| 76 |
+
debug_mode=False,
|
| 77 |
+
description=(
|
| 78 |
+
"Agent polyvalent spécialisé dans l'analyse, la génération et la vérification des directives de suivi des projets et programmes de l'État du Sénégal. "
|
| 79 |
+
"Il combine les rôles suivants : analyse des requêtes, recherche contextuelle, extraction des rôles, rédaction structurée et contrôle de conformité."
|
| 80 |
+
),
|
| 81 |
+
instructions=[
|
| 82 |
+
# === CONTEXTE GLOBAL ===
|
| 83 |
+
"### Contexte Global\n"
|
| 84 |
+
"- Les directives proviennent de toutes les sources officielles : Conseils Présidentiels, Conseils des Ministres, Conseils Interministériels et réunions gouvernementales.\n"
|
| 85 |
+
"- Extraire **toutes les directives** présentes, même implicites ou partiellement mentionnées.\n"
|
| 86 |
+
"- L’objectif est de fournir une base exhaustive et directement exploitable pour le suivi des projets et programmes de l’État.\n",
|
| 87 |
+
|
| 88 |
+
# === TÂCHE 1 : RECHERCHE ET EXTRACTION ===
|
| 89 |
+
"### Tâche : Extraction exhaustive des directives\n"
|
| 90 |
+
"- Identifier toutes les directives liées à `{query}` ou aux projets/programmes mentionnés dans les documents.\n"
|
| 91 |
+
"- Ne pas omettre les directives secondaires ou implicites (ex : relances, rappels, suivis techniques).\n"
|
| 92 |
+
"- Lister chaque directive de manière indépendante.\n",
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
# === FORMAT DE SORTIE OBLIGATOIRE ===
|
| 96 |
+
"### Format de sortie obligatoire\n"
|
| 97 |
+
"- La sortie doit être **en Markdown structuré** avec deux sections principales :\n"
|
| 98 |
+
"1. **Résumé Global** : courte synthèse expliquant le contexte et nombre de directives trouvées.\n"
|
| 99 |
+
"2. **Détails des Directives** sous forme de tableau Markdown avec colonnes suivantes :\n"
|
| 100 |
+
"| Autorité émettrice | Source | Date de publication | Description détaillée de la directive | Action(s) à entreprendre | Structures responsables | Parties prenantes | Statut | Priorité | Date limite | Actions spécifiques du BOCS |\n"
|
| 101 |
+
"- Chaque ligne représente une directive.\n"
|
| 102 |
+
"- Les dates doivent être formatées `JJ Mois AAAA` .\n"
|
| 103 |
+
"- Ne pas inclure de JSON ou code brut, uniquement résumé + tableau Markdown.\n",
|
| 104 |
+
|
| 105 |
+
# === TÂCHE 2 : MÉTADONNÉES ET STRUCTURATION ===
|
| 106 |
+
"### Tâche : Structuration des directives\n"
|
| 107 |
+
"- Pour chaque directive, fournir :\n"
|
| 108 |
+
" 1. **Autorité émettrice** : Auteur de la directive (PR, PM, SGG, Ministère, etc.)\n"
|
| 109 |
+
" 2. **Source** (type de conseil ou réunion)\n"
|
| 110 |
+
" 3. **Date de publication du document** (ou 'Date du conseil')\n"
|
| 111 |
+
" 4. **Description détaillée** de la directive\n"
|
| 112 |
+
" 5. **Actions à entreprendre** (liste détaillée)\n"
|
| 113 |
+
" 6. **Structures responsables** (entités principales)\n"
|
| 114 |
+
" 7. **Parties prenantes** (internes/externes)\n"
|
| 115 |
+
" 8. **Statut** (En cours, Démarré, Non démarré)\n"
|
| 116 |
+
" 9. **Priorité** (Très Haute, Haute, Moyenne, Basse)\n"
|
| 117 |
+
" 10. **Date limite** (ou 'Meilleurs delais')\n"
|
| 118 |
+
" 11. **Actions spécifiques du BOCS** (toujours présentes, détaillées)\n",
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
# === TÂCHE 4 : VÉRIFICATION ===
|
| 123 |
+
"### Vérification\n"
|
| 124 |
+
"- Vérifier que chaque directive contient **tous les champs**.\n"
|
| 125 |
+
"- Si une information manque au niveau de la date limite, préciser 'Meilleurs delais'.\n"
|
| 126 |
+
"- Garantir que la structure est prête à être utilisée sans retravail manuel."
|
| 127 |
+
],
|
| 128 |
+
tools=[ReasoningTools(add_instructions=True)],
|
| 129 |
+
storage=storage,
|
| 130 |
+
add_history_to_messages=False,
|
| 131 |
+
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
# Vérifier si la collection Qdrant contient déjà des points
|
| 136 |
+
def is_collection_indexed(vector_db):
|
| 137 |
+
try:
|
| 138 |
+
info = vector_db.client.count(collection_name=vector_db.collection)
|
| 139 |
+
return info.count > 0
|
| 140 |
+
except Exception:
|
| 141 |
+
return False
|
| 142 |
+
|
| 143 |
+
# Charger la base de connaissance seulement si elle n'est pas déjà indexée
|
| 144 |
+
if not is_collection_indexed(vector_db):
|
| 145 |
+
agent.knowledge.load(recreate=False)
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
# -------------------------------
|
| 149 |
+
# Fonction principale : appel agent avec agent.run()
|
| 150 |
+
# -------------------------------
|
| 151 |
+
def generate_directives(query: str):
|
| 152 |
+
try:
|
| 153 |
+
prompt = f"Analyse toutes les directives en details en occurence avec la question du user : {query}"
|
| 154 |
+
|
| 155 |
+
result = agent.run(prompt)
|
| 156 |
+
content = getattr(result, "content", None)
|
| 157 |
+
|
| 158 |
+
if hasattr(content, "dict") and callable(content.dict):
|
| 159 |
+
data = content.dict()
|
| 160 |
+
elif isinstance(content, dict):
|
| 161 |
+
data = content
|
| 162 |
+
elif isinstance(content, str):
|
| 163 |
+
return f"Réponse brute :\n\n{content}"
|
| 164 |
+
else:
|
| 165 |
+
return f"Type inattendu pour content : {type(content)}\nValeur : {content}"
|
| 166 |
+
|
| 167 |
+
resume = data.get("resume")
|
| 168 |
+
directives = data.get("directives")
|
| 169 |
+
if not resume or not directives:
|
| 170 |
+
return "Le JSON ne contient pas 'resume' ou 'directives'."
|
| 171 |
+
|
| 172 |
+
# Générer seulement du Markdown
|
| 173 |
+
markdown_output = f"## Résumé\n- **Nombre de directives** : {resume.get('nombre_directives', 'Inconnu')}\n\n"
|
| 174 |
+
markdown_output += f"> {resume.get('contexte', '')}\n\n"
|
| 175 |
+
markdown_output += "## Directives détaillées\n"
|
| 176 |
+
for idx, directive in enumerate(directives, start=1):
|
| 177 |
+
markdown_output += f"### Directive {idx}\n"
|
| 178 |
+
for key, value in directive.items():
|
| 179 |
+
if isinstance(value, list):
|
| 180 |
+
value = ", ".join(value)
|
| 181 |
+
markdown_output += f"- **{key}** : {value}\n"
|
| 182 |
+
markdown_output += "\n"
|
| 183 |
+
|
| 184 |
+
return markdown_output
|
| 185 |
+
|
| 186 |
+
except Exception as e:
|
| 187 |
+
return f"Erreur inattendue : {str(e)}"
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
# --- Convertir Markdown en PDF ---
|
| 191 |
+
def export_to_pdf(markdown_text):
|
| 192 |
+
if not markdown_text:
|
| 193 |
+
return None
|
| 194 |
+
# Convertir Markdown en HTML
|
| 195 |
+
html_content = md.markdown(markdown_text)
|
| 196 |
+
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf")
|
| 197 |
+
HTML(string=html_content).write_pdf(tmp.name)
|
| 198 |
+
return tmp.name
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def export_to_html(markdown_text):
|
| 202 |
+
if not markdown_text:
|
| 203 |
+
return None
|
| 204 |
+
html_content = md.markdown(markdown_text)
|
| 205 |
+
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".html")
|
| 206 |
+
with open(tmp.name, "w", encoding="utf-8") as f:
|
| 207 |
+
f.write(f"<html><body>{html_content}</body></html>")
|
| 208 |
+
return tmp.name
|
| 209 |
+
|
| 210 |
+
# -------------------------------
|
| 211 |
+
# Interface Gradio avec deux onglets
|
| 212 |
+
# -------------------------------
|
| 213 |
+
with gr.Blocks() as demo:
|
| 214 |
+
gr.Markdown("# 🏛️ Assistant Suivi des Directives")
|
| 215 |
+
|
| 216 |
+
# Onglet Chat
|
| 217 |
+
with gr.Tab("Chat"):
|
| 218 |
+
with gr.Row():
|
| 219 |
+
query_input = gr.Textbox(
|
| 220 |
+
label="Votre requête",
|
| 221 |
+
placeholder="Ex: Détails spécifiques sur des directives individuelles. ",
|
| 222 |
+
lines=3
|
| 223 |
+
)
|
| 224 |
+
submit_btn = gr.Button("Générer Directives")
|
| 225 |
+
|
| 226 |
+
output_md = gr.Markdown(label="Résultat")
|
| 227 |
+
|
| 228 |
+
# Ici on relie directement la fonction
|
| 229 |
+
submit_btn.click(
|
| 230 |
+
fn=generate_directives,
|
| 231 |
+
inputs=query_input,
|
| 232 |
+
outputs=output_md
|
| 233 |
+
)
|
| 234 |
+
|
| 235 |
+
# Onglet Export (toujours à l’intérieur du même Blocks)
|
| 236 |
+
with gr.Tab("Export"):
|
| 237 |
+
gr.Markdown("### Exportez les directives générées")
|
| 238 |
+
export_pdf_btn = gr.Button("Télécharger PDF (.pdf)")
|
| 239 |
+
export_html_btn = gr.Button("Télécharger HTML (.html)")
|
| 240 |
+
|
| 241 |
+
pdf_file = gr.File(label="Fichier PDF", interactive=False)
|
| 242 |
+
html_file = gr.File(label="Fichier HTML", interactive=False)
|
| 243 |
+
|
| 244 |
+
export_pdf_btn.click(fn=export_to_pdf, inputs=output_md, outputs=pdf_file)
|
| 245 |
+
export_html_btn.click(fn=export_to_html, inputs=output_md, outputs=html_file)
|
| 246 |
+
|
| 247 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|
requirements.txt
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
agno==1.7.5
|
| 2 |
+
aiofiles==24.1.0
|
| 3 |
+
annotated-types==0.7.0
|
| 4 |
+
anyio==4.9.0
|
| 5 |
+
argon2-cffi==25.1.0
|
| 6 |
+
argon2-cffi-bindings==21.2.0
|
| 7 |
+
beautifulsoup4==4.13.4
|
| 8 |
+
bidict==0.23.1
|
| 9 |
+
Brotli==1.1.0
|
| 10 |
+
bs4==0.0.2
|
| 11 |
+
certifi==2025.7.14
|
| 12 |
+
cffi==1.17.1
|
| 13 |
+
charset-normalizer==3.4.2
|
| 14 |
+
click==8.2.1
|
| 15 |
+
cssselect2==0.8.0
|
| 16 |
+
defusedxml==0.7.1
|
| 17 |
+
deprecation==2.1.0
|
| 18 |
+
distro==1.9.0
|
| 19 |
+
docstring_parser==0.17.0
|
| 20 |
+
fastapi==0.116.1
|
| 21 |
+
ffmpy==0.6.1
|
| 22 |
+
filelock==3.18.0
|
| 23 |
+
fonttools==4.59.0
|
| 24 |
+
fsspec==2025.7.0
|
| 25 |
+
gitdb==4.0.12
|
| 26 |
+
GitPython==3.1.44
|
| 27 |
+
gradio==5.38.2
|
| 28 |
+
gradio_client==1.11.0
|
| 29 |
+
groovy==0.1.2
|
| 30 |
+
grpcio==1.73.1
|
| 31 |
+
h11==0.16.0
|
| 32 |
+
h2==4.2.0
|
| 33 |
+
hf-xet==1.1.5
|
| 34 |
+
hpack==4.1.0
|
| 35 |
+
httpcore==1.0.9
|
| 36 |
+
httptools==0.6.4
|
| 37 |
+
httpx==0.28.1
|
| 38 |
+
huggingface-hub==0.34.1
|
| 39 |
+
hyperframe==6.1.0
|
| 40 |
+
idna==3.10
|
| 41 |
+
Jinja2==3.1.6
|
| 42 |
+
jiter==0.10.0
|
| 43 |
+
lancedb==0.24.1
|
| 44 |
+
lxml==6.0.0
|
| 45 |
+
Markdown==3.8.2
|
| 46 |
+
markdown-it-py==3.0.0
|
| 47 |
+
MarkupSafe==3.0.2
|
| 48 |
+
mdurl==0.1.2
|
| 49 |
+
minio==7.2.16
|
| 50 |
+
numpy==2.3.1
|
| 51 |
+
openai==1.97.1
|
| 52 |
+
orjson==3.11.1
|
| 53 |
+
overrides==7.7.0
|
| 54 |
+
packaging==25.0
|
| 55 |
+
panda==0.3.1
|
| 56 |
+
pandas==2.3.1
|
| 57 |
+
pillow==11.3.0
|
| 58 |
+
portalocker==3.2.0
|
| 59 |
+
protobuf==6.31.1
|
| 60 |
+
pyarrow==21.0.0
|
| 61 |
+
pycparser==2.22
|
| 62 |
+
pycryptodome==3.23.0
|
| 63 |
+
pydantic==2.11.7
|
| 64 |
+
pydantic-settings==2.10.1
|
| 65 |
+
pydantic_core==2.33.2
|
| 66 |
+
pydub==0.25.1
|
| 67 |
+
pydyf==0.11.0
|
| 68 |
+
Pygments==2.19.2
|
| 69 |
+
pylance==0.31.1
|
| 70 |
+
pypdf==5.8.0
|
| 71 |
+
pyphen==0.17.2
|
| 72 |
+
python-dateutil==2.9.0.post0
|
| 73 |
+
python-docx==1.2.0
|
| 74 |
+
python-dotenv==1.1.1
|
| 75 |
+
python-engineio==4.12.2
|
| 76 |
+
python-multipart==0.0.20
|
| 77 |
+
python-socketio==5.13.0
|
| 78 |
+
pytz==2025.2
|
| 79 |
+
PyYAML==6.0.2
|
| 80 |
+
qdrant-client==1.15.0
|
| 81 |
+
redis==6.2.0
|
| 82 |
+
requests==2.32.4
|
| 83 |
+
rich==14.0.0
|
| 84 |
+
ruff==0.12.5
|
| 85 |
+
safehttpx==0.1.6
|
| 86 |
+
semantic-version==2.10.0
|
| 87 |
+
setuptools==80.9.0
|
| 88 |
+
shellingham==1.5.4
|
| 89 |
+
simple-websocket==1.1.0
|
| 90 |
+
six==1.17.0
|
| 91 |
+
smmap==5.0.2
|
| 92 |
+
sniffio==1.3.1
|
| 93 |
+
soupsieve==2.7
|
| 94 |
+
starlette==0.47.2
|
| 95 |
+
tantivy==0.24.0
|
| 96 |
+
tinycss2==1.4.0
|
| 97 |
+
tinyhtml5==2.0.0
|
| 98 |
+
tomli==2.2.1
|
| 99 |
+
tomlkit==0.13.3
|
| 100 |
+
tqdm==4.67.1
|
| 101 |
+
typer==0.16.0
|
| 102 |
+
typing-inspection==0.4.1
|
| 103 |
+
typing_extensions==4.14.1
|
| 104 |
+
tzdata==2025.2
|
| 105 |
+
unicorn==2.1.3
|
| 106 |
+
urllib3==2.5.0
|
| 107 |
+
uvicorn==0.35.0
|
| 108 |
+
uvloop==0.21.0
|
| 109 |
+
watchfiles==1.1.0
|
| 110 |
+
weasyprint==66.0
|
| 111 |
+
webencodings==0.5.1
|
| 112 |
+
websockets==15.0.1
|
| 113 |
+
wsproto==1.2.0
|
| 114 |
+
youtube-search-python==1.6.6
|
| 115 |
+
youtube-transcript-api==0.6.2
|
| 116 |
+
zopfli==0.2.3.post1
|