Refactorisation du code, correction des quelques bugs, Clear de Readme principal, Ajout de l'entrée utilisateur pour la modification du nom de projet
This commit is contained in:
118
README.md
118
README.md
@@ -2,78 +2,45 @@
|
|||||||
|
|
||||||
## Contexte du Projet
|
## Contexte du Projet
|
||||||
ARC est une plateforme d'automatisation du développement logiciel basée sur un workflow multi-agents (IA). Le système orchestre plusieurs modèles d'IA spécialisés pour transformer un besoin utilisateur en un code source validé, testé et stocké.
|
ARC est une plateforme d'automatisation du développement logiciel basée sur un workflow multi-agents (IA). Le système orchestre plusieurs modèles d'IA spécialisés pour transformer un besoin utilisateur en un code source validé, testé et stocké.
|
||||||
___
|
|
||||||
### Étape 0 : Préparation de l'environnement du projet
|
|
||||||
Tâches :
|
|
||||||
- Créer backend minimal
|
|
||||||
- Créer modèle de données simple
|
|
||||||
- langGraph
|
|
||||||
___
|
|
||||||
### Étape 1 : Analyse du Besoin & Qualification (Agent PM / Business Analyst)
|
|
||||||
- L'utilisateur entre une demande en langage naturel.
|
|
||||||
- **Agent 1 (PM)** analyse la demande. Si des informations manquent pour coder, il pose des questions clarificatrices à l'utilisateur jusqu'à obtenir un cahier des charges complet.
|
|
||||||
- **Vérification BDD :** Avant de coder, le système cherche dans une base de données vectorielle si un projet similaire existe déjà.
|
|
||||||
- *Si oui :* On propose le lien à l'utilisateur. Si l'utilisateur valide, le workflow s'arrête ici.
|
|
||||||
- *Si non (ou si l'utilisateur rejette l'existant) :* On passe à l'étape 2.
|
|
||||||
|
|
||||||
Outils :
|
|
||||||
- LangGraph -> LangGraph est adapté aux workflows multi‑agents avec états, transitions conditionnelles, persistance et human‑in‑the‑loop
|
|
||||||
- Python -> backend, les agents, les appels LLM, les tests, les embeddings et les intégrations + compatible avec autres tools
|
|
||||||
- Pydantic AI / Pydantic -> forcer l’Agent PM à produire un cahier des charges structuré
|
|
||||||
- Chainlit -> adapté aux interfaces conversationnelles
|
|
||||||
BDD :
|
|
||||||
- Qdrant -> adapté à la recherche sémantique + stocker les embeddings de projets/scripts
|
|
||||||
- Snowflake Arctic Embed 2.0 -> modèle d’embedding
|
|
||||||
- Redis (optionnel) -> cache de recherche ;sessions utilisateur ;état temporaire ;verrouillage d’un workflow ;file d’attente simple
|
|
||||||
___
|
|
||||||
### Étape 2 : Génération de Code (Agent Développeur)
|
|
||||||
- **Agent 2 (Dev)** reçoit le cahier des charges validé et génère l'arborescence et le code source du projet.
|
|
||||||
|
|
||||||
Outils :
|
|
||||||
- LangGraph -> LangGraph est adapté aux workflows multi‑agents avec états, transitions conditionnelles, persistance et human‑in‑the‑loop
|
|
||||||
- Mistral ou Gemma (modèle trop généraliste/léger->tache simple) -> DeepSeek Coder/Qwen2.5
|
|
||||||
- vLLM -> meilleur choix qu’Ollama pour une plateforme plus industrialisée. Llama.cpp modèles quantifiés sur CPU ou machines modestes + moins adapté à une plateforme multi‑utilisateur
|
|
||||||
- Pydantic
|
|
||||||
___
|
|
||||||
### Étape 3 : Tests et Assurance Qualité (Agent QA / Testeur)
|
|
||||||
- **Agent 3 (QA)** récupère le code de l'Agent 2. Il doit exécuter le code (via une sandbox sécurisée) ou générer/exécuter des tests unitaires pour vérifier la qualité, la sécurité et le fonctionnement.
|
|
||||||
- **Boucle de correction automatique (Loop 1) :** Si les tests échouent, l'Agent 3 renvoie les erreurs à l'Agent 2 avec les logs. L'Agent 2 corrige et renvoie à l'Agent 3. Cette boucle tourne au maximum 3 fois jusqu'à ce que le code soit "vert".
|
|
||||||
- **EXTENSION FUTURE** si les tests échouent 3 fois, envoyé à une IA plus puissante.
|
|
||||||
|
|
||||||
Outils :
|
|
||||||
- Docker
|
|
||||||
- Ruff -> qualité de code
|
|
||||||
- Bandit -> sécurité
|
|
||||||
- Semgrep (optionnel) -> règles de sécurité et qualité plus larges
|
|
||||||
Boucle correction :
|
|
||||||
- LangGraph
|
|
||||||
- Pydantic
|
|
||||||
___
|
|
||||||
### Étape 4 : Livraison & Feedback Utilisateur (Boucle Humaine)
|
|
||||||
- Une fois le code validé par l'Agent 3, il est présenté à l'utilisateur.
|
|
||||||
- L'utilisateur teste et valide.
|
|
||||||
- *Si Validé :* Le projet est sauvegardé dans la base de données (pour la recherche de l'Étape 1) et livré (ex: zip ou dépôt GitHub).
|
|
||||||
- *Si Refusé :* L'utilisateur indique ce qui ne va pas. Tout le contexte (code actuel + retours) est renvoyé à l'**Étape 1** pour réanalyse, et le cycle recommence.
|
|
||||||
|
|
||||||
Outils :
|
|
||||||
- Chainlit
|
|
||||||
- Git
|
|
||||||
___
|
|
||||||
### Étape finale :
|
|
||||||
|
|
||||||
Tâches :
|
|
||||||
- Tests fonctionnels de bout en bout
|
|
||||||
- Sécurité minimale
|
|
||||||
- Documentation finale
|
|
||||||
- Présentation
|
|
||||||
|
|
||||||
## Lancer le projet
|
## Lancer le projet
|
||||||
|
|
||||||
|
Avant de lancer le projet, il est important de créer le fichier .env dans le dossier */backend* et de remplir les éléments suivant :
|
||||||
```bash
|
```bash
|
||||||
chmod +x run_project.sh
|
LLM_BASE_URL=
|
||||||
./run_project.sh
|
LLM_API_KEY=
|
||||||
|
LLM_MODEL=
|
||||||
|
LLM_MODEL_DEV=
|
||||||
|
LLM_MODEL_QA=
|
||||||
|
EMBEDDING_BASE_URL=
|
||||||
|
EMBEDDING_MODEL=
|
||||||
|
GITEA_ADMIN_USER=
|
||||||
|
GITEA_ADMIN_PASSWORD=
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Une fois les informations fournies, éxectuer la commande suivante pour lancer le projet
|
||||||
|
```bash
|
||||||
|
python .\orchestrator.py
|
||||||
|
```
|
||||||
|
|
||||||
|
* L'interface utilisateur conversationnelle se trouve sur l'adresse suivant : http://localhost:8001/
|
||||||
|
* Le Gitea se trouve à l'adresse suivante : http://localhost:3000/
|
||||||
|
___
|
||||||
|
|
||||||
|
## Arrêter le projet
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python .\stop_project.py
|
||||||
|
```
|
||||||
|
___
|
||||||
|
|
||||||
|
## Visualiser les logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -p arc-app logs -f
|
||||||
|
```
|
||||||
|
___
|
||||||
|
|
||||||
## Structure du projet
|
## Structure du projet
|
||||||
```bash
|
```bash
|
||||||
ARC/
|
ARC/
|
||||||
@@ -85,15 +52,13 @@ ARC/
|
|||||||
│ │ │ └── qa_agent.py
|
│ │ │ └── qa_agent.py
|
||||||
│ │ │
|
│ │ │
|
||||||
│ │ ├── api/
|
│ │ ├── api/
|
||||||
│ │ │ ├── routes/
|
│ │ │ └── routes/
|
||||||
│ │ │ │ ├── health.py
|
│ │ │ ├── health.py
|
||||||
│ │ │ │ └── workflow.py
|
│ │ │ └── workflow.py
|
||||||
│ │ │ └── deps.py
|
|
||||||
│ │ │
|
│ │ │
|
||||||
│ │ ├── core/
|
│ │ ├── core/
|
||||||
│ │ │ ├── config.py
|
│ │ │ ├── config.py
|
||||||
│ │ │ ├── logging.py
|
│ │ │ └── logging.py
|
||||||
│ │ │ └── security.py
|
|
||||||
│ │ │
|
│ │ │
|
||||||
│ │ ├── graph/ # LangGraph
|
│ │ ├── graph/ # LangGraph
|
||||||
│ │ │ ├── state.py
|
│ │ │ ├── state.py
|
||||||
@@ -103,10 +68,6 @@ ARC/
|
|||||||
│ │ ├── llm/ # appels modèles
|
│ │ ├── llm/ # appels modèles
|
||||||
│ │ │ ├── client.py # wrapper d’appel
|
│ │ │ ├── client.py # wrapper d’appel
|
||||||
│ │ │ ├── prompts.py # prompts centralisés
|
│ │ │ ├── prompts.py # prompts centralisés
|
||||||
│ │ │ └── providers.py # Gemma/llama.cpp....
|
|
||||||
│ │ │
|
|
||||||
│ │ ├── models/ # modèles métier / persistance (métadonnées d’un projet/version/statut/lien Git/hash/tags)
|
|
||||||
│ │ │ └── project.py
|
|
||||||
│ │ │
|
│ │ │
|
||||||
│ │ ├── repositories/ # accès externes, Qdrant / Redis / stockage
|
│ │ ├── repositories/ # accès externes, Qdrant / Redis / stockage
|
||||||
│ │ │ ├── qdrant_repository.py
|
│ │ │ ├── qdrant_repository.py
|
||||||
@@ -122,8 +83,7 @@ ARC/
|
|||||||
│ │ │ ├── api.py
|
│ │ │ ├── api.py
|
||||||
│ │ │ ├── spec.py
|
│ │ │ ├── spec.py
|
||||||
│ │ │ ├── code_output.py
|
│ │ │ ├── code_output.py
|
||||||
│ │ │ ├── qa_report.py
|
│ │ │ └── qa_report.py
|
||||||
│ │ │ └── project.py
|
|
||||||
│ │ │
|
│ │ │
|
||||||
│ │ ├── services/
|
│ │ ├── services/
|
||||||
│ │ │ ├── workflow_service.py
|
│ │ │ ├── workflow_service.py
|
||||||
@@ -146,7 +106,7 @@ ARC/
|
|||||||
│ │ ├── test_qdrant.py
|
│ │ ├── test_qdrant.py
|
||||||
│ │ └── test_snowflake.py
|
│ │ └── test_snowflake.py
|
||||||
│ │
|
│ │
|
||||||
│ ├── .env
|
│ ├── .env # IMPORTANT élément à remplir
|
||||||
│ ├── chainlit_app.py
|
│ ├── chainlit_app.py
|
||||||
│ ├── chainlit_fr_FR.md
|
│ ├── chainlit_fr_FR.md
|
||||||
│ ├── docker-compose-ai.yml # Conteneurs pour les agents et l'embedding
|
│ ├── docker-compose-ai.yml # Conteneurs pour les agents et l'embedding
|
||||||
|
|||||||
@@ -1,52 +1,67 @@
|
|||||||
# ARC Backend
|
# ARC Backend
|
||||||
|
|
||||||
Backend minimal pour le projet ARC :
|
## Organisation du projet
|
||||||
- API FastAPI
|
|
||||||
- orchestration LangGraph
|
|
||||||
- agents PM / Dev / QA
|
|
||||||
- interface Chainlit
|
|
||||||
- intégration future Qdrant / Redis / vLLM
|
|
||||||
|
|
||||||
## Installation
|
### Étape 0 : Préparation de l'environnement du projet
|
||||||
|
Tâches :
|
||||||
|
- Créer backend minimal
|
||||||
|
- Créer modèle de données simple
|
||||||
|
- langGraph
|
||||||
|
___
|
||||||
|
### Étape 1 : Analyse du Besoin & Qualification (Agent PM / Business Analyst)
|
||||||
|
- L'utilisateur entre une demande en langage naturel.
|
||||||
|
- **Agent 1 (PM)** analyse la demande. Si des informations manquent pour coder, il pose des questions clarificatrices à l'utilisateur jusqu'à obtenir un cahier des charges complet.
|
||||||
|
- **Vérification BDD :** Avant de coder, le système cherche dans une base de données vectorielle si un projet similaire existe déjà.
|
||||||
|
- *Si oui :* On propose le lien à l'utilisateur. Si l'utilisateur valide, le workflow s'arrête ici.
|
||||||
|
- *Si non (ou si l'utilisateur rejette l'existant) :* On passe à l'étape 2.
|
||||||
|
|
||||||
```bash
|
Outils :
|
||||||
python -m venv .venv
|
- LangGraph -> LangGraph est adapté aux workflows multi‑agents avec états, transitions conditionnelles, persistance et human‑in‑the‑loop
|
||||||
.venv\Scripts\activate
|
- Python -> backend, les agents, les appels LLM, les tests, les embeddings et les intégrations + compatible avec autres tools
|
||||||
pip install -r requirements.txt
|
- Pydantic AI / Pydantic -> forcer l’Agent PM à produire un cahier des charges structuré
|
||||||
uvicorn app.main:app --reload --port 8000
|
- Chainlit -> adapté aux interfaces conversationnelles
|
||||||
```
|
BDD :
|
||||||
|
- Qdrant (optionnel) -> adapté à la recherche sémantique + stocker les embeddings de projets/scripts
|
||||||
|
- Snowflake Arctic Embed 2.0 (optionnel) -> modèle d’embedding
|
||||||
|
- Redis (optionnel) -> cache de recherche ;sessions utilisateur ;état temporaire ;verrouillage d’un workflow ;file d’attente simple
|
||||||
|
___
|
||||||
|
### Étape 2 : Génération de Code (Agent Développeur)
|
||||||
|
- **Agent 2 (Dev)** reçoit le cahier des charges validé et génère l'arborescence et le code source du projet.
|
||||||
|
|
||||||
## Lancer Chainlit
|
Outils :
|
||||||
|
- LangGraph -> LangGraph est adapté aux workflows multi‑agents avec états, transitions conditionnelles, persistance et human‑in‑the‑loop
|
||||||
|
- Mistral ou Gemma (modèle trop généraliste/léger->tache simple) -> DeepSeek Coder/Qwen2.5
|
||||||
|
- vLLM -> meilleur choix qu’Ollama pour une plateforme plus industrialisée. Llama.cpp modèles quantifiés sur CPU ou machines modestes + moins adapté à une plateforme multi‑utilisateur
|
||||||
|
- Pydantic
|
||||||
|
- Gitea
|
||||||
|
___
|
||||||
|
### Étape 3 : Tests et Assurance Qualité (Agent QA / Testeur)
|
||||||
|
- **Agent 3 (QA)** récupère le code de l'Agent 2. Il doit exécuter le code (via une sandbox sécurisée) ou générer/exécuter des tests unitaires pour vérifier la qualité, la sécurité et le fonctionnement.
|
||||||
|
- **Boucle de correction automatique (Loop 1) :** Si les tests échouent, l'Agent 3 renvoie les erreurs à l'Agent 2 avec les logs. L'Agent 2 corrige et renvoie à l'Agent 3. Cette boucle tourne au maximum 3 fois jusqu'à ce que le code soit "vert".
|
||||||
|
- **EXTENSION FUTURE** si les tests échouent 3 fois, envoyé à une IA plus puissante.
|
||||||
|
|
||||||
```bash
|
Outils :
|
||||||
chainlit run chainlit_app.py --port 8001
|
- Docker
|
||||||
```
|
- Semgrep -> règles de sécurité et qualité plus larges
|
||||||
|
Boucle correction :
|
||||||
|
- world model (optionnel)
|
||||||
|
- LangGraph
|
||||||
|
- Pydantic
|
||||||
|
___
|
||||||
|
### Étape 4 : Livraison & Feedback Utilisateur (Boucle Humaine)
|
||||||
|
- Une fois le code validé par l'Agent 3, il est présenté à l'utilisateur.
|
||||||
|
- L'utilisateur teste et valide.
|
||||||
|
- *Si Validé :* Le projet est sauvegardé dans la base de données (pour la recherche de l'Étape 1) et livré (ex: zip ou dépôt GitHub).
|
||||||
|
- *Si Refusé :* L'utilisateur indique ce qui ne va pas. Tout le contexte (code actuel + retours) est renvoyé à l'**Étape 1** pour réanalyse, et le cycle recommence.
|
||||||
|
|
||||||
## Lancement auto
|
Outils :
|
||||||
|
- Chainlit
|
||||||
|
- Gitea
|
||||||
|
___
|
||||||
|
### Étape finale :
|
||||||
|
|
||||||
```bash
|
Tâches :
|
||||||
python orchestrator.py
|
- Tests fonctionnels de bout en bout
|
||||||
```
|
- Sécurité minimale
|
||||||
|
- Documentation finale
|
||||||
## Arrêt propre
|
- Présentation
|
||||||
|
|
||||||
```bash
|
|
||||||
python stop_project.py
|
|
||||||
```
|
|
||||||
|
|
||||||
# Logs
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose -p arc-app logs -f
|
|
||||||
docker compose -p arc-ai logs -f
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python .\tests\test_snowflake.py
|
|
||||||
docker compose exec app python tests/test_qdrant.py
|
|
||||||
```
|
|
||||||
|
|
||||||
API dispo sur :
|
|
||||||
- http://127.0.0.1:8001
|
|
||||||
@@ -56,7 +56,7 @@ async def run_dev_agent(spec: dict, qa_feedback: list = None, repo_url: str = No
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[Dev Agent] ❌ Échec de la génération/validation : {type(e).__name__}: {str(e)}")
|
logger.error(f"[Dev Agent] ❌ Échec de la génération/validation : {type(e).__name__}: {str(e)}")
|
||||||
return _generate_fallback_code_output(spec)
|
return _generate_fallback_code_output(spec, repo_url=repo_url, files=files)
|
||||||
|
|
||||||
async def _ensure_gitea_repo(base_name: str, repo_url: str = None) -> str:
|
async def _ensure_gitea_repo(base_name: str, repo_url: str = None) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -189,18 +189,46 @@ async def _deploy_to_gitea(code_data: ProjectCodeOutput, project_title: str, rep
|
|||||||
if os.path.exists(work_dir):
|
if os.path.exists(work_dir):
|
||||||
shutil.rmtree(work_dir)
|
shutil.rmtree(work_dir)
|
||||||
|
|
||||||
def _generate_fallback_code_output(spec: dict) -> dict:
|
def _generate_fallback_code_output(spec: dict, repo_url: str = None, files: list = None) -> dict:
|
||||||
"""Génère un livrable minimal de secours en cas de crash du LLM."""
|
"""
|
||||||
|
Génère un livrable de secours. Récupère et reconstruit l'état du code
|
||||||
|
à partir des fichiers existants s'ils sont fournis, sinon génère un README d'erreur.
|
||||||
|
"""
|
||||||
logger.warning("[Dev Agent] Génération du package de secours (Fallback)")
|
logger.warning("[Dev Agent] Génération du package de secours (Fallback)")
|
||||||
title = spec.get("title", "automation_script")
|
title = spec.get("title", "automation_script")
|
||||||
|
|
||||||
fallback = ProjectCodeOutput(
|
fallback_files = []
|
||||||
spec_title=title,
|
fallback_tree = []
|
||||||
tree=["main.py", "README.md", "requirements.txt"],
|
|
||||||
files=[
|
if files:
|
||||||
GeneratedFile(path="main.py", content="import logging\nlogging.basicConfig(level=logging.INFO)\n\ndef main():\n logging.error('Le Dev Agent a rencontré une erreur de génération.')\n\nif __name__ == '__main__':\n main()"),
|
for f in files:
|
||||||
GeneratedFile(path="README.md", content=f"# {title}\nGénération en mode fallback suite à une erreur technique."),
|
path = ""
|
||||||
GeneratedFile(path="requirements.txt", content="# Aucune dépendance externe définie (Fallback)\n")
|
content = ""
|
||||||
|
|
||||||
|
if isinstance(f, dict):
|
||||||
|
path = f.get("path") or f.get("filename") or f.get("name") or ""
|
||||||
|
content = f.get("content") or f.get("code") or ""
|
||||||
|
elif hasattr(f, "path") and hasattr(f, "content"):
|
||||||
|
path = getattr(f, "path", "")
|
||||||
|
content = getattr(f, "content", "")
|
||||||
|
|
||||||
|
if path:
|
||||||
|
fallback_files.append(GeneratedFile(path=path, content=content))
|
||||||
|
fallback_tree.append(path)
|
||||||
|
|
||||||
|
if not fallback_files:
|
||||||
|
fallback_files = [
|
||||||
|
GeneratedFile(
|
||||||
|
path="README.md",
|
||||||
|
content=f"# {title}\nErreur interne : le code n'a pas pu être généré par l'agent Dev lors du premier essai."
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
fallback_tree = ["README.md"]
|
||||||
|
|
||||||
|
fallback = ProjectCodeOutput(
|
||||||
|
repo_url=repo_url,
|
||||||
|
spec_title=title,
|
||||||
|
tree=fallback_tree,
|
||||||
|
files=fallback_files
|
||||||
)
|
)
|
||||||
return fallback.model_dump()
|
return fallback.model_dump()
|
||||||
@@ -38,7 +38,7 @@ DERNIÈRE PRÉCISION / INPUT UTILISATEUR :
|
|||||||
{user_input}
|
{user_input}
|
||||||
|
|
||||||
IMPORTANT : Tu dois impérativement combiner les contraintes énoncées précédemment (comme le langage de programmation choisi, l'OS, etc.) avec cette nouvelle information pour enrichir la spécification existante.
|
IMPORTANT : Tu dois impérativement combiner les contraintes énoncées précédemment (comme le langage de programmation choisi, l'OS, etc.) avec cette nouvelle information pour enrichir la spécification existante.
|
||||||
Réponds UNIQUEMENT avec du JSON valide. Aucun texte avant le JSON, aucun texte après le JSON."""
|
"""
|
||||||
))
|
))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ class Settings(BaseSettings):
|
|||||||
llm_model_dev: str
|
llm_model_dev: str
|
||||||
llm_model_qa: str
|
llm_model_qa: str
|
||||||
|
|
||||||
embedding_base_url: str = "http://localhost:8002/v1"
|
embedding_base_url: str
|
||||||
embedding_model: str = "snowflake-arctic-embed-m-v1.5"
|
embedding_model: str
|
||||||
|
|
||||||
gitea_base_url: str
|
gitea_base_url: str = "http://git-arc:3000"
|
||||||
gitea_api_url: str
|
gitea_api_url: str = "http://git-arc:3000/api/v1"
|
||||||
gitea_admin_user: str
|
gitea_admin_user: str
|
||||||
gitea_admin_password: str
|
gitea_admin_password: str
|
||||||
gitea_token: Optional[str] = None
|
gitea_token: Optional[str] = None
|
||||||
|
|||||||
@@ -1,45 +1,21 @@
|
|||||||
PM_AGENT_PROMPT = """Tu es un Product Manager senior spécialisé en structuration de cahiers des charges.
|
PM_AGENT_PROMPT = """Tu es un Product Manager senior et un Lead Developer spécialisé en structuration de cahiers des charges techniques.
|
||||||
|
|
||||||
=== TÂCHE UNIQUE ET EXCLUSIVE ===
|
=== TÂCHE UNIQUE ET EXCLUSIVE ===
|
||||||
Tu reçois une demande utilisateur brute.
|
Tu reçois une demande utilisateur brute. Tu dois l'analyser et la transformer en un JSON valide suivant EXACTEMENT la structure "ProjectSpec".
|
||||||
Tu DOIS la transformer en JSON valide suivant EXACTEMENT cette structure (ProjectSpec).
|
|
||||||
|
|
||||||
⚠️ RÈGLE ABSOLUE : Tu génères UNIQUEMENT du JSON valide. Pas de texte avant, pas de texte après.
|
⚠️ RÈGLE DE SORTIE : Tu génères UNIQUEMENT du JSON valide. Pas de texte avant, pas de texte après, pas de balises markdown de code block (pas de ```json).
|
||||||
Si tu ne comprends pas un champ, tu le remplis avec une valeur par défaut INTELLIGENTE.
|
Si tu ne comprends pas un champ ou si l'info est absente mais non critique, remplis-le avec une valeur par défaut INTELLIGENTE.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
=== STRUCTURE JSON À RETOURNER ===
|
=== GESTION PARTICULIÈRE : CAS DU HORS-SUJET ===
|
||||||
|
Si la demande de l'utilisateur n'est PAS un projet de développement, de script ou d'automatisation informatique (ex: "Comment on fait des bébés ?", "Rédige un poème", "Donne-moi une recette de cuisine") :
|
||||||
{
|
1. "title" : "Hors Sujet"
|
||||||
"title": "...",
|
2. "description" : "La demande ne concerne pas un projet de développement ou d'automatisation."
|
||||||
"description": "...",
|
3. "requirements" : ["La demande utilisateur doit être reformulée pour cibler un script ou une tâche d'automatisation."]
|
||||||
"requirements": ["...", "..."],
|
4. "is_complete" : false
|
||||||
"constraints": ["..."],
|
5. "clarifying_question" : "Désolé, l'outil ARC est conçu exclusivement pour concevoir, automatiser et développer des scripts ou des projets informatiques. Votre demande ne semble pas correspondre à un projet de développement. Comment puis-je vous aider sur un sujet technique ou d'automatisation ?"
|
||||||
"language": "...",
|
6. Remplis tous les autres champs avec des valeurs vides ou par défaut cohérentes.
|
||||||
"target_stack": "...",
|
|
||||||
"io_config": {
|
|
||||||
"has_inputs": true/false,
|
|
||||||
"input_type": "file|directory|api|database|none",
|
|
||||||
"input_paths_or_sources": ["..."],
|
|
||||||
"has_outputs": true/false,
|
|
||||||
"output_type": "file|directory|database|api_response|log_only",
|
|
||||||
"output_formats": ["..."]
|
|
||||||
},
|
|
||||||
"auth_config": {
|
|
||||||
"requires_auth": true/false,
|
|
||||||
"auth_method": "env_variables|service_account_json|oauth2|api_key|none",
|
|
||||||
"target_tools_and_apis": ["..."]
|
|
||||||
},
|
|
||||||
"env_config": {
|
|
||||||
"target_os": "linux|windows|macos|cross_platform",
|
|
||||||
"python_version": "^3.11",
|
|
||||||
"critical_dependencies": ["..."]
|
|
||||||
},
|
|
||||||
"error_handling_strategy": "abort_on_error|log_and_continue|retry_policy",
|
|
||||||
"is_complete": true,
|
|
||||||
"clarifying_question": null
|
|
||||||
}
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -47,253 +23,81 @@ Si tu ne comprends pas un champ, tu le remplis avec une valeur par défaut INTEL
|
|||||||
|
|
||||||
**1. title** :
|
**1. title** :
|
||||||
- JAMAIS vide. Jamais null.
|
- JAMAIS vide. Jamais null.
|
||||||
- Si vague: génère un titre court et clair dérivé du besoin
|
|
||||||
- Format: "Verb + Object" (ex: "CSV Data Cleaner", "API Report Generator")
|
- Format: "Verb + Object" (ex: "CSV Data Cleaner", "API Report Generator")
|
||||||
- Si l'utilisateur dit "teste mon code" → "Code Test Runner"
|
- Si l'utilisateur dit juste "script" -> "Automation Script"
|
||||||
- Si l'utilisateur dit juste "script" → "Automation Script"
|
|
||||||
|
|
||||||
**2. description** :
|
**2. description** :
|
||||||
- JAMAIS vide. Jamais null.
|
- JAMAIS vide. Jamais null.
|
||||||
- 1-2 phrases qui expliquent QUOI et POURQUOI.
|
- 1-2 phrases qui expliquent QUOI et POURQUOI de manière cohérente avec la demande.
|
||||||
- Réutilise les mots clés de la demande pour que ce soit cohérent.
|
|
||||||
- Ex: "Traite les fichiers CSV mensuels. Filtre les anomalies et génère un rapport Excel."
|
|
||||||
|
|
||||||
**3. requirements** :
|
**3. requirements** :
|
||||||
- JAMAIS vide. JAMAIS une liste vide [].
|
- JAMAIS vide. JAMAIS une liste vide []. MINIMUM 3 items détaillés (même déduits intelligemment).
|
||||||
- MINIMUM 3 items (même générés intelligemment).
|
- Format: "Action: Détail" ou "Étape N: Description"
|
||||||
- Format: "Action: Détail" ou "Étape N: Décrire l'action"
|
|
||||||
- Ex:
|
|
||||||
[
|
|
||||||
"Lire les données CSV depuis le dossier /input",
|
|
||||||
"Valider l'intégrité des colonnes requises",
|
|
||||||
"Générer un rapport Excel avec statistiques"
|
|
||||||
]
|
|
||||||
|
|
||||||
**4. constraints** :
|
**4. constraints** :
|
||||||
- Peux être vide si pas mentionné.
|
- Peut être vide []. Sinon, normes, formats ou limites (ex: ["PEP 8", "Logs rotatifs"]).
|
||||||
- Sinon: normes de code, limitations, formats (ex: ["PEP 8 strict", "Logs rotatifs"])
|
|
||||||
|
|
||||||
**5. language** :
|
**5. language** :
|
||||||
- JAMAIS vide.
|
- Par défaut: "Python" (Sauf si spécifié autrement comme Go, Bash, Node.js).
|
||||||
- Par défaut: "Python"
|
|
||||||
- Sauf si l'utilisateur dit "Node.js", "Go", etc.
|
|
||||||
|
|
||||||
**6. target_stack** :
|
**6. target_stack** :
|
||||||
- Peux être vide
|
- Framework ou outil spécifique (ex: "FastAPI", "Pandas"). Peut être vide.
|
||||||
- Sauf si l'utilisateur dit "FastAPI", "Django", etc.
|
|
||||||
|
|
||||||
**7. io_config.has_inputs** :
|
**7. io_config.has_inputs** :
|
||||||
- TRUE si l'utilisateur mentionne : "fichier", "dossier", "données", "lire", "importer", "télécharger", "API", "base de données", "table"
|
- TRUE si la demande mentionne : lire, importer, fichier, dossier, API, base de données, table, etc.
|
||||||
- FALSE sinon.
|
- FALSE sinon. Si TRUE -> input_type DOIT être rempli (pas "none").
|
||||||
- ⚠️ Si TRUE → input_type DOIT être rempli (pas null)
|
|
||||||
|
|
||||||
**8. io_config.input_type** :
|
**8. io_config.input_type** :
|
||||||
- DÉDUIS du contexte :
|
- Déduis du contexte ("file", "directory", "api", "database"). "none" si pas d'inputs.
|
||||||
- "fichier CSV/Excel/JSON/PDF/TXT" → "file"
|
|
||||||
- "dossier" → "directory"
|
|
||||||
- "API REST/GraphQL/Service Web" → "api"
|
|
||||||
- "base de données/table SQL/MongoDB" → "database"
|
|
||||||
- Aucun input → "none"
|
|
||||||
- ❌ JAMAIS null si has_inputs=true
|
|
||||||
|
|
||||||
**9. io_config.has_outputs** :
|
**9. io_config.has_outputs** :
|
||||||
- TRUE si l'utilisateur mentionne : "exporter", "générer", "créer", "sauvegarder", "envoyer", "résultat", "fichier", "rapport"
|
- TRUE si la demande mentionne : générer, exporter, créer, sauvegarder, envoyer, rapport, fichier, etc.
|
||||||
- FALSE sinon.
|
|
||||||
|
|
||||||
**10. io_config.output_type** :
|
**10. io_config.output_type** :
|
||||||
- DÉDUIS du contexte :
|
- Déduis du contexte ("file", "directory", "database", "api_response", "log_only").
|
||||||
- "fichier Excel/CSV/PDF/JSON" → "file"
|
|
||||||
- "dossier de résultats" → "directory"
|
|
||||||
- "base de données" → "database"
|
|
||||||
- "appel API/réponse HTTP" → "api_response"
|
|
||||||
- "juste des logs/console" → "log_only"
|
|
||||||
- ❌ JAMAIS null si has_outputs=true
|
|
||||||
|
|
||||||
**11. io_config.output_formats** :
|
**11. io_config.output_formats** :
|
||||||
- Formats de sortie mentionnés (ex: ["CSV", "JSON", "XLSX", "PDF"])
|
- Liste de formats (ex: ["CSV", "XLSX"]). Peut être vide [].
|
||||||
- Peux être vide si has_outputs=false
|
|
||||||
|
|
||||||
**12. auth_config.requires_auth** :
|
**12. auth_config.requires_auth** :
|
||||||
- TRUE si l'utilisateur mentionne : "API", "token", "key", "clé", "authentification", "compte", "service account", "OAuth", "login", "credential", "Jira", "SharePoint", "GCP", "AWS", "Azure", "base de données"
|
- TRUE si la demande interagit avec un service tiers, une API ou une base de données nécessitant des accès (ex: Jira, Zabbix, SharePoint, DB SQL, API externe, GCP, AWS).
|
||||||
- FALSE sinon.
|
|
||||||
|
|
||||||
**13. auth_config.auth_method** :
|
**13. auth_config.auth_method** :
|
||||||
- DÉDUIS du contexte :
|
- Déduis intelligemment ("env_variables", "service_account_json", "oauth2", "api_key"). "none" si pas d'authentification.
|
||||||
- "variables d'environnement/.env" → "env_variables"
|
|
||||||
- "fichier .json avec credentials" → "service_account_json"
|
|
||||||
- "OAuth/OpenID" → "oauth2"
|
|
||||||
- "API key/token" → "api_key"
|
|
||||||
- Pas d'auth → "none"
|
|
||||||
- ⚠️ Si requires_auth=true → DÉDUIS intelligemment, jamais null
|
|
||||||
|
|
||||||
**14. auth_config.target_tools_and_apis** :
|
**14. auth_config.target_tools_and_apis** :
|
||||||
- Liste les services externes (ex: ["Jira API", "Google Drive", "AWS S3"])
|
- Services cibles à lister (ex: ["Jira API"]). Peut être vide [].
|
||||||
- Peux être vide si requires_auth=false
|
|
||||||
|
|
||||||
**15. env_config.target_os** :
|
**15. env_config.target_os** :
|
||||||
- "cross_platform" par défaut
|
- "cross_platform" par défaut (Sauf si linux, windows, macos est spécifié).
|
||||||
- Sauf si mentionné : "linux", "windows", "macos"
|
|
||||||
|
|
||||||
**16. env_config.language_version** :
|
**16. env_config.language_version** :
|
||||||
- "^3.11" par défaut si Python
|
- "^3.11" par défaut pour Python. Sinon la version mentionnée.
|
||||||
- Sauf si la version exacte est mentionnée par l'utilisateur (ex: "Python 3.12", "Node.js 18", "Go 1.20")
|
|
||||||
- Sinon vide
|
|
||||||
|
|
||||||
**17. env_config.critical_dependencies** :
|
**17. env_config.critical_dependencies** :
|
||||||
- DÉDUIS du contexte :
|
- Déduis du contexte (ex: "pandas", "requests", "sqlalchemy", "openpyxl"). Inclure toujours: ["logging"].
|
||||||
- Données tabulaires → "pandas"
|
|
||||||
- API REST → "requests"
|
|
||||||
- Configuration → "pydantic"
|
|
||||||
- Base de données → "sqlalchemy", "pymongo"
|
|
||||||
- Fichiers → "openpyxl", "python-docx", "PyPDF2"
|
|
||||||
- Parallelisation → "asyncio", "concurrent.futures"
|
|
||||||
- Toujours inclure: ["logging"] (natif mais important)
|
|
||||||
|
|
||||||
**18. error_handling_strategy** :
|
**18. error_handling_strategy** :
|
||||||
- JAMAIS vide. JAMAIS null.
|
- "log_and_continue" par défaut. "abort_on_error" ou "retry_policy" si la tolérance aux pannes est mentionnée.
|
||||||
- "log_and_continue" par défaut (robuste)
|
|
||||||
- "abort_on_error" si critique
|
|
||||||
- "retry_policy" si mentionné "retry", "robustesse", "résilience"
|
|
||||||
|
|
||||||
**19. is_complete** :
|
**19. is_complete** :
|
||||||
- TRUE si tu as pu remplir tous les champs principaux (title, description, requirements) sans ambiguïté
|
- Définis impérativement à FALSE si :
|
||||||
- FALSE UNIQUEMENT si quelque chose est vraiment manquant et ambigu
|
- La demande est Hors Sujet (voir section GESTION DU HORS-SUJET).
|
||||||
|
- Le script doit interagir avec un serveur ou un équipement mais il manque l'adresse IP, le nom d'hôte ou le sous-réseau.
|
||||||
|
- Le script doit requêter un service ou une API (ex: Jira, SharePoint, Zabbix) mais il manque le lien (URL de l'instance/du serveur).
|
||||||
|
- Le script nécessite une authentification mais le type d'accès (compte de service, clé API, token) ou le compte n'est pas spécifié.
|
||||||
|
- Les traitements, calculs ou filtres à appliquer sur les données entrantes restent flous ou ambigus.
|
||||||
|
- Définis à TRUE uniquement si tous ces éléments fonctionnels et d'accès sont clairs et exploitables immédiatement par un développeur.
|
||||||
|
|
||||||
**20. clarifying_question** :
|
**20. clarifying_question** :
|
||||||
- null si is_complete=true
|
- Doit être "null" si is_complete est TRUE.
|
||||||
- Sinon: une question naturelle et polie pour l'utilisateur
|
- Si is_complete est FALSE : Rédige **UNE SEULE** question polie, claire et ciblée pour débloquer le développement.
|
||||||
- Ex: "Quel format de fichier en entrée (CSV, Excel, JSON) et où souhaitez-vous la sortie ?"
|
- *Exemple de question technique* : "Pourriez-vous me fournir l'adresse IP du serveur cible ainsi que le type de compte de service requis pour l'authentification ?"
|
||||||
|
- *Exemple de question hors-sujet* : Voir la section "GESTION PARTICULIÈRE : CAS DU HORS-SUJET" ci-dessus.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
=== EXEMPLES CONCRETS ===
|
=== MAINTENANT, ANALYSE LA DEMANDE CI-DESSOUS ET GÉNÈRE LE JSON ===
|
||||||
|
|
||||||
### Input 1: "Je veux un script en Ruby qui lit un CSV et l'exporte en Excel"
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"title": "CSV to Excel Converter",
|
|
||||||
"description": "Lit un fichier CSV, valide les données et exporte le résultat en format Excel (.xlsx).",
|
|
||||||
"requirements": [
|
|
||||||
"Lire le fichier CSV depuis le dossier source",
|
|
||||||
"Valider les colonnes requises",
|
|
||||||
"Exporter en fichier Excel avec formatage"
|
|
||||||
],
|
|
||||||
"constraints": ["PEP 8 strict"],
|
|
||||||
"language": "Ruby",
|
|
||||||
"target_stack": "",
|
|
||||||
"io_config": {
|
|
||||||
"has_inputs": true,
|
|
||||||
"input_type": "file",
|
|
||||||
"input_paths_or_sources": ["input.csv"],
|
|
||||||
"has_outputs": true,
|
|
||||||
"output_type": "file",
|
|
||||||
"output_formats": ["XLSX"]
|
|
||||||
},
|
|
||||||
"auth_config": {
|
|
||||||
"requires_auth": false,
|
|
||||||
"auth_method": "none",
|
|
||||||
"target_tools_and_apis": []
|
|
||||||
},
|
|
||||||
"env_config": {
|
|
||||||
"target_os": "cross_platform",
|
|
||||||
"language_version": "3.2.1O",
|
|
||||||
"critical_dependencies": ["pandas", "openpyxl"]
|
|
||||||
},
|
|
||||||
"error_handling_strategy": "log_and_continue",
|
|
||||||
"is_complete": true,
|
|
||||||
"clarifying_question": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Input 2: "Je dois récupérer des données sur une API Zabbix et les stocker en Python 3.12"
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"title": "API Data Fetcher",
|
|
||||||
"description": "Récupère les données depuis une API, les valide et les stocke en base de données.",
|
|
||||||
"requirements": [
|
|
||||||
"Appeler l'API avec authentification",
|
|
||||||
"Parser les données JSON",
|
|
||||||
"Insérer ou mettre à jour les enregistrements en base de données"
|
|
||||||
],
|
|
||||||
"constraints": [],
|
|
||||||
"language": "Python",
|
|
||||||
"target_stack": "Zabbix API",
|
|
||||||
"io_config": {
|
|
||||||
"has_inputs": true,
|
|
||||||
"input_type": "api",
|
|
||||||
"input_paths_or_sources": ["https://api.example.com/data"],
|
|
||||||
"has_outputs": true,
|
|
||||||
"output_type": "database",
|
|
||||||
"output_formats": ["SQL", "JSON"]
|
|
||||||
},
|
|
||||||
"auth_config": {
|
|
||||||
"requires_auth": true,
|
|
||||||
"auth_method": "api_key",
|
|
||||||
"target_tools_and_apis": ["REST API"]
|
|
||||||
},
|
|
||||||
"env_config": {
|
|
||||||
"target_os": "cross_platform",
|
|
||||||
"language_version": "3.12",
|
|
||||||
"critical_dependencies": ["requests", "sqlalchemy", "pydantic"]
|
|
||||||
},
|
|
||||||
"error_handling_strategy": "retry_policy",
|
|
||||||
"is_complete": true,
|
|
||||||
"clarifying_question": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
---
|
|
||||||
|
|
||||||
=== VALIDATION DE COMPLÉTUDE DE LA SPÉCIFICATION (OBLIGATOIRE) ===
|
|
||||||
|
|
||||||
Avant de considérer la ProjectSpec comme complète, tu dois te demander :
|
|
||||||
|
|
||||||
"Un développeur senior peut-il implémenter l'intégralité du projet sans devoir faire la moindre hypothèse importante ?"
|
|
||||||
|
|
||||||
Tu dois vérifier notamment que les éléments suivants sont suffisamment définis lorsqu'ils sont pertinents :
|
|
||||||
|
|
||||||
- le comportement attendu de l'application ;
|
|
||||||
- les données d'entrée (type, format, provenance) ;
|
|
||||||
- les données de sortie (type, format, emplacement) ;
|
|
||||||
- les règles métier ;
|
|
||||||
- les traitements à effectuer ;
|
|
||||||
- les services, API ou bases de données concernés ;
|
|
||||||
- les méthodes d'authentification ;
|
|
||||||
- les paramètres configurables ;
|
|
||||||
- les cas limites importants ;
|
|
||||||
- les contraintes fonctionnelles ou techniques.
|
|
||||||
|
|
||||||
Si une ou plusieurs informations indispensables sont absentes ou ambiguës, tu dois :
|
|
||||||
|
|
||||||
- définir `"is_complete": false`
|
|
||||||
- remplir `"clarifying_question"` avec UNE question claire, précise et ciblée permettant d'obtenir uniquement les informations manquantes.
|
|
||||||
- ne jamais inventer une règle métier ou un comportement qui n'a pas été demandé.
|
|
||||||
|
|
||||||
Tu peux compléter automatiquement uniquement les conventions techniques raisonnables (OS, stratégie de logs, dépendances, versions, structure du projet, etc.), mais jamais les besoins fonctionnels.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
=== DERNIER CHECKPOINT ===
|
|
||||||
|
|
||||||
Avant de retourner le JSON :
|
|
||||||
1. ✅ title rempli ? (pas vide, pas null)
|
|
||||||
2. ✅ description rempli ? (pas vide, pas null)
|
|
||||||
3. ✅ requirements rempli ? (pas vide, 3+ items)
|
|
||||||
4. ✅ target_stack rempli ? (pas vide, pas null)
|
|
||||||
5. ✅ error_handling_strategy rempli ? (pas vide, pas null)
|
|
||||||
6. ✅ Si has_inputs=true → input_type rempli ? (pas null)
|
|
||||||
7. ✅ Si has_outputs=true → output_type rempli ? (pas null)
|
|
||||||
8. ✅ Si requires_auth=true → auth_method rempli ? (pas null, pas "none")
|
|
||||||
|
|
||||||
Si tout est ✅, tu retournes le JSON.
|
|
||||||
Si un champ fail, tu génères une VALEUR PAR DÉFAUT INTELLIGENTE et marque is_complete=false.
|
|
||||||
|
|
||||||
CRITICAL: Si la demande de l'utilisateur est trop courte, trop vague (ex: 'Je veux un script Ruby'), ou manque d'objectifs clairs, tu DOIS impérativement définir is_complete à false et formuler une question précise dans clarifying_question pour lui demander ce que le script doit faire concrètement.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
=== MAINTENANT, ANALYSE ET GÉNÈRE LE JSON ===
|
|
||||||
|
|
||||||
Ci-dessous la demande utilisateur brute. Tu dois transformer cela en JSON ProjectSpec valide, rempli et cohérent.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectRecord(BaseModel):
|
|
||||||
id: Optional[str] = None
|
|
||||||
title: str
|
|
||||||
summary: str
|
|
||||||
tags: List[str] = []
|
|
||||||
repository_url: Optional[str] = None
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectSummary(BaseModel):
|
|
||||||
id: Optional[str] = None
|
|
||||||
title: str
|
|
||||||
summary: str
|
|
||||||
tags: List[str] = []
|
|
||||||
repository_url: Optional[str] = None
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import chainlit as cl
|
import chainlit as cl
|
||||||
import asyncio
|
import unicodedata
|
||||||
import zipfile
|
import zipfile
|
||||||
import httpx
|
import httpx
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import io
|
import io
|
||||||
|
import re
|
||||||
|
|
||||||
# --- CONFIGURATION GITEA ---
|
# --- CONFIGURATION GITEA ---
|
||||||
GITEA_TOKEN = os.getenv("GITEA_TOKEN", "ton_token_gitea_ici")
|
GITEA_TOKEN = os.getenv("GITEA_TOKEN", "ton_token_gitea_ici")
|
||||||
@@ -143,7 +144,7 @@ def inject_conforme_badge(state: dict) -> dict:
|
|||||||
return state
|
return state
|
||||||
|
|
||||||
|
|
||||||
async def reset_to_start(message_text: str = "Bonjour 👋 Je suis ARC. Décris-moi ton besoin logiciel."):
|
async def reset_to_start(message_text: str = "Bonjour 👋 Je suis ARC. Décrivez-moi votre besoin logiciel."):
|
||||||
"""
|
"""
|
||||||
Réinitialise complètement l'état de la session utilisateur.
|
Réinitialise complètement l'état de la session utilisateur.
|
||||||
"""
|
"""
|
||||||
@@ -166,6 +167,25 @@ async def reset_to_start(message_text: str = "Bonjour 👋 Je suis ARC. Décris-
|
|||||||
|
|
||||||
await cl.Message(content=message_text).send()
|
await cl.Message(content=message_text).send()
|
||||||
|
|
||||||
|
def sanitize_repo_name(name: str) -> str:
|
||||||
|
"""
|
||||||
|
Nettoie une chaîne pour la rendre 100% compatible avec un nom de dépôt Git.
|
||||||
|
Gère les accents, les espaces et supprime les caractères interdits.
|
||||||
|
"""
|
||||||
|
if not name:
|
||||||
|
return "mon-projet"
|
||||||
|
|
||||||
|
normalized = unicodedata.normalize('NFKD', name)
|
||||||
|
no_accent = normalized.encode('ascii', 'ignore').decode('utf-8')
|
||||||
|
lowered = no_accent.lower()
|
||||||
|
spaced_to_hyphen = lowered.replace(" ", "_")
|
||||||
|
|
||||||
|
cleaned = re.sub(r'[^a-z0-9-_]', '', spaced_to_hyphen)
|
||||||
|
cleaned = re.sub(r'-+', '-', cleaned)
|
||||||
|
cleaned = re.sub(r'_+', '_', cleaned)
|
||||||
|
|
||||||
|
return cleaned.strip('-_')
|
||||||
|
|
||||||
async def render_workflow_state(new_state: dict):
|
async def render_workflow_state(new_state: dict):
|
||||||
"""
|
"""
|
||||||
Fonction centrale pour aiguiller l'affichage Chainlit
|
Fonction centrale pour aiguiller l'affichage Chainlit
|
||||||
@@ -181,7 +201,7 @@ async def render_workflow_state(new_state: dict):
|
|||||||
elif current_status == "spec_ready":
|
elif current_status == "spec_ready":
|
||||||
spec = new_state.get("spec", {})
|
spec = new_state.get("spec", {})
|
||||||
|
|
||||||
summary = "### Éléments importants à retenir de ton projet :\n\n"
|
summary = "### Éléments importants à retenir de votre projet :\n\n"
|
||||||
summary += f"- **Nom du projet** : {spec.get('title')}\n"
|
summary += f"- **Nom du projet** : {spec.get('title')}\n"
|
||||||
summary += f"- **Description** : {spec.get('description')}\n"
|
summary += f"- **Description** : {spec.get('description')}\n"
|
||||||
summary += "- **Actions** :\n"
|
summary += "- **Actions** :\n"
|
||||||
@@ -189,10 +209,12 @@ async def render_workflow_state(new_state: dict):
|
|||||||
summary += "- **Contraintes** :\n"
|
summary += "- **Contraintes** :\n"
|
||||||
summary += "\n".join(f" - {constraint}" for constraint in spec.get("constraints", [])) + "\n"
|
summary += "\n".join(f" - {constraint}" for constraint in spec.get("constraints", [])) + "\n"
|
||||||
summary += f"- **Langage** : {spec.get('language')}\n\n"
|
summary += f"- **Langage** : {spec.get('language')}\n\n"
|
||||||
summary += "**Est-ce que cela vous convient ?**"
|
summary += "💡 *(Le nom du projet indiqué pourra être modifié par la suite)*"
|
||||||
|
|
||||||
|
await cl.Message(content=summary).send()
|
||||||
|
|
||||||
res = await cl.AskActionMessage(
|
res = await cl.AskActionMessage(
|
||||||
content=summary,
|
content="**Est-ce que cela vous convient ?**",
|
||||||
actions=[
|
actions=[
|
||||||
cl.Action(name="oui", payload={"value": "oui"}, label="Oui, c'est parfait 👍"),
|
cl.Action(name="oui", payload={"value": "oui"}, label="Oui, c'est parfait 👍"),
|
||||||
cl.Action(name="non", payload={"value": "non"}, label="Non, modifier ❌")
|
cl.Action(name="non", payload={"value": "non"}, label="Non, modifier ❌")
|
||||||
@@ -207,6 +229,34 @@ async def render_workflow_state(new_state: dict):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if res and res.get("name") == "oui":
|
if res and res.get("name") == "oui":
|
||||||
|
proposed_title = spec.get('title', 'mon_projet')
|
||||||
|
|
||||||
|
name_choice = await cl.AskActionMessage(
|
||||||
|
content=f"Le nom proposé pour le projet est : **{proposed_title}**.\nSouhaitez-vous le conserver ou le modifier ?",
|
||||||
|
actions=[
|
||||||
|
cl.Action(name="garder_nom", payload={"value": "keep"}, label="Conserver ce nom 🏷️"),
|
||||||
|
cl.Action(name="modifier_nom", payload={"value": "change"}, label="Choisir un autre nom ✏️")
|
||||||
|
],
|
||||||
|
timeout=3600
|
||||||
|
).send()
|
||||||
|
|
||||||
|
if name_choice is None:
|
||||||
|
await cl.Message(content="⏰ **Session expirée.** Envoie un message pour reprendre.").send()
|
||||||
|
return
|
||||||
|
|
||||||
|
if name_choice.get("name") == "modifier_nom":
|
||||||
|
new_name_res = await cl.AskUserMessage(
|
||||||
|
content="Saisissez le nouveau nom de votre projet : 👇",
|
||||||
|
timeout=600
|
||||||
|
).send()
|
||||||
|
|
||||||
|
if new_name_res and new_name_res.get("output"):
|
||||||
|
raw_title = new_name_res["output"].strip()
|
||||||
|
custom_title = sanitize_repo_name(raw_title)
|
||||||
|
spec['title'] = custom_title
|
||||||
|
new_state['spec'] = spec
|
||||||
|
await cl.Message(content=f"🏷️ Nom du projet configuré sur : **{custom_title}**").send()
|
||||||
|
|
||||||
await cl.Message(content="🚀 **Spécifications validées !** Lancement de la génération du code...").send()
|
await cl.Message(content="🚀 **Spécifications validées !** Lancement de la génération du code...").send()
|
||||||
|
|
||||||
new_state["status"] = "spec_approved"
|
new_state["status"] = "spec_approved"
|
||||||
@@ -216,7 +266,7 @@ async def render_workflow_state(new_state: dict):
|
|||||||
response = await client.post(
|
response = await client.post(
|
||||||
"http://127.0.0.1:8000/api/workflow/run",
|
"http://127.0.0.1:8000/api/workflow/run",
|
||||||
json=new_state,
|
json=new_state,
|
||||||
timeout=600.0
|
timeout=1200.0
|
||||||
)
|
)
|
||||||
final_state = response.json()
|
final_state = response.json()
|
||||||
cl.user_session.set("graph_state", final_state)
|
cl.user_session.set("graph_state", final_state)
|
||||||
@@ -228,7 +278,7 @@ async def render_workflow_state(new_state: dict):
|
|||||||
cl.user_session.set("graph_state", new_state)
|
cl.user_session.set("graph_state", new_state)
|
||||||
|
|
||||||
await cl.Message(
|
await cl.Message(
|
||||||
content="🔄 **Compris.** Qu'est-ce qui ne convient pas ? S'il te plaît, précise les éléments manquants ou à corriger :"
|
content="🔄 **Compris.** Qu'est-ce qui ne convient pas ? S'il vous plaît, précisez les éléments manquants ou à corriger :"
|
||||||
).send()
|
).send()
|
||||||
|
|
||||||
elif current_status in ["wait_human_review", "approved_by_human"]:
|
elif current_status in ["wait_human_review", "approved_by_human"]:
|
||||||
@@ -334,7 +384,7 @@ async def render_workflow_state(new_state: dict):
|
|||||||
cl.user_session.set("graph_state", new_state)
|
cl.user_session.set("graph_state", new_state)
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
resp = await client.post("http://127.0.0.1:8000/api/workflow/run", json=new_state, timeout=600.0)
|
resp = await client.post("http://127.0.0.1:8000/api/workflow/run", json=new_state, timeout=1200.0)
|
||||||
|
|
||||||
final_state = resp.json()
|
final_state = resp.json()
|
||||||
cl.user_session.set("graph_state", final_state)
|
cl.user_session.set("graph_state", final_state)
|
||||||
@@ -342,7 +392,7 @@ async def render_workflow_state(new_state: dict):
|
|||||||
elif res and res.get("name") == "refuser_projet":
|
elif res and res.get("name") == "refuser_projet":
|
||||||
feedback_user = await cl.AskUserMessage(
|
feedback_user = await cl.AskUserMessage(
|
||||||
content="Veuillez décrire les corrections ou les modifications à apporter au projet.",
|
content="Veuillez décrire les corrections ou les modifications à apporter au projet.",
|
||||||
timeout=600
|
timeout=1200
|
||||||
).send()
|
).send()
|
||||||
|
|
||||||
if feedback_user:
|
if feedback_user:
|
||||||
@@ -353,7 +403,7 @@ async def render_workflow_state(new_state: dict):
|
|||||||
await cl.Message(content="🔄 **Feedback transmis.** Prise en compte des modifications...").send()
|
await cl.Message(content="🔄 **Feedback transmis.** Prise en compte des modifications...").send()
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
resp = await client.post("http://127.0.0.1:8000/api/workflow/run", json=new_state, timeout=600.0)
|
resp = await client.post("http://127.0.0.1:8000/api/workflow/run", json=new_state, timeout=1200.0)
|
||||||
|
|
||||||
loop_state = resp.json()
|
loop_state = resp.json()
|
||||||
cl.user_session.set("graph_state", loop_state)
|
cl.user_session.set("graph_state", loop_state)
|
||||||
@@ -392,13 +442,13 @@ async def render_workflow_state(new_state: dict):
|
|||||||
if res.get("name") == "recommencer_projet":
|
if res.get("name") == "recommencer_projet":
|
||||||
reset_msg = (
|
reset_msg = (
|
||||||
"🔄 **Ancien projet supprimé avec succès.**\n\n"
|
"🔄 **Ancien projet supprimé avec succès.**\n\n"
|
||||||
"Faisons table rase ! Décris-moi ton besoin logiciel pour repartir sur de nouvelles bases : 👇"
|
"Faisons table rase ! Décrivez-moi votre besoin logiciel pour repartir sur de nouvelles bases : 👇"
|
||||||
)
|
)
|
||||||
await reset_to_start(reset_msg)
|
await reset_to_start(reset_msg)
|
||||||
else:
|
else:
|
||||||
reset_msg = (
|
reset_msg = (
|
||||||
"❌ **Session fermée et dépôt nettoyé.**\n\n"
|
"❌ **Session fermée et dépôt nettoyé.**\n\n"
|
||||||
"Si tu as un nouveau besoin à soumettre plus tard, envoie simplement un message pour démarrer."
|
"Si vous avez un nouveau besoin à soumettre plus tard, envoie simplement un message pour démarrer."
|
||||||
)
|
)
|
||||||
await reset_to_start(reset_msg)
|
await reset_to_start(reset_msg)
|
||||||
|
|
||||||
@@ -466,7 +516,7 @@ async def on_message(message: cl.Message):
|
|||||||
response = await client.post(
|
response = await client.post(
|
||||||
"http://127.0.0.1:8000/api/workflow/run",
|
"http://127.0.0.1:8000/api/workflow/run",
|
||||||
json=state,
|
json=state,
|
||||||
timeout=600.0
|
timeout=1200.0
|
||||||
)
|
)
|
||||||
|
|
||||||
new_state = response.json()
|
new_state = response.json()
|
||||||
|
|||||||
Reference in New Issue
Block a user