diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..bd9b929 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,174 @@ +# Changelog - Collaborative Design System + +## Version 2.0 - Collaborative Document Design (Nouvelle Fonctionnalité) + +### 🎯 Concept Principal +Remplacement du système de débat parallèle par un système de **conception itérative collaborative** où : +- Un architecte lead crée un document initial complet +- 3-7 agents spécialisés révisent et améliorent le document tour par tour +- Le document évolue progressivement jusqu'à convergence naturelle +- Traçabilité complète de toutes les modifications + +### Backend - Changements + +#### 1. **Base de Données** (`src/db/schema.js`) +- ✅ Nouvelles tables : + - `collaborative_sessions` : Sessions de conception + - `document_versions` : Historique complet des versions + - `document_rounds` : Suivi des tours de table +- Maintien de la compatibilité rétro avec `debates` et `responses` + +#### 2. **Service d'Orchestration** (`src/services/collaborativeOrchestrator.js` - NOUVEAU) +- `createSession()` : Crée une nouvelle session collaborative +- `startCollaborativeSession()` : Lead Architect crée le document initial +- `runRound()` : Exécute un tour de revue (chaque agent revoit le document) +- `getSessionDetails()` : Récupère l'état complet avec historique +- `calculateDiff()` : Suivi des modifications entre versions +- Détection automatique de convergence (aucun changement d'un tour complet) + +#### 3. **Routes API** (`src/routes/collaborate.js` - NOUVEAU) +``` +POST /api/collaborate - Créer une session +POST /api/collaborate/:id/start - Démarrer la session +POST /api/collaborate/:id/round - Exécuter un tour de revue +GET /api/collaborate/:id - Obtenir les détails +GET /api/collaborate/:id/document - Télécharger le document actuel +GET /api/collaborate/:id/versions/:v - Obtenir une version spécifique +POST /api/collaborate/:id/complete - Terminer la session +``` + +#### 4. **Serveur Principal** (`src/index.js`) +- ✅ Intégration du nouvel orchestrateur collaboratif +- ✅ Support WebSocket pour `sessionId` en plus de `debateId` +- Routage automatique vers `/api/collaborate` + +### Frontend - Changements + +#### 1. **Store Pinia** (`src/stores/collaboration.js` - NOUVEAU) +- Gestion d'état des sessions collaboratives +- Fonctions pour créer, lancer, et suivre les sessions +- Méthodes de téléchargement de documents + +#### 2. **Composants Vue** + +**CollaborativeInput.vue** (NOUVEAU) +- Formulaire pour créer une nouvelle session +- Choix du format (Markdown/Texte) +- Sélection du nombre d'agents (3-7) +- Interface conviviale avec explications + +**CollaborativeSession.vue** (NOUVEAU) +- Affichage en temps réel du document en évolution +- Timeline des modifications avec agents impliqués +- Boutons de contrôle : + - "Next Review Round" : Lancer le tour suivant + - "Complete Session" : Terminer + - "Download" : Exporter le document + - "Timeline" : Voir l'historique +- Affichage du statut de convergence + +**DocumentViewer.vue** (NOUVEAU) +- Rendu du document en Markdown ou texte brut +- Mise en évidence syntaxique simple +- Support des liens, listes, code blocks +- Responsive et bien stylisé + +#### 3. **App.vue** - Refonte Majeure +- Écran de sélection du mode au démarrage +- 2 modes : "Classic Debate" vs "Collaborative Design" (Recommandé) +- Navigation fluide entre les modes +- Boutons de retour intuitifs + +#### 4. **Composable WebSocket Amélioré** (`src/composables/useWebSocket.js`) +- Support de `sessionId` en paramètre +- Rétro-compatible avec `debateId` +- Routing automatique du WebSocket + +### Améliorations Produit + +#### 1. **7 Agents Spécialisés** (vs 4 avant) +- Lead Architect (créateur du document initial, poids 1.5x dans anciennes versions) +- Backend Engineer +- Frontend Engineer +- UI/UX Designer +- DevOps Engineer (NOUVEAU) +- Product Manager (NOUVEAU) +- Security Specialist (NOUVEAU) + +#### 2. **Flux Itératif Intelligent** +- Chaque agent voit le document complet + historique +- System prompts spécialisés pour chaque rôle +- Détection de convergence automatique +- Arrêt quand aucune modification d'un tour complet + +#### 3. **Documents Flexibles** +- Format Markdown (.md) pour documents structurés +- Format Texte brut (.txt) pour contenu simple +- Versioning complet avec historique +- Export facile en un clic + +#### 4. **Traçabilité Complète** +- Historique de chaque modification +- Qui a changé quoi et pourquoi +- Timeline visuelle interactive +- Accès à n'importe quelle version + +### Avantages du Nouveau Système + +| Aspect | Mode Débat | Mode Collaboratif | +|--------|-----------|------------------| +| **Processus** | Parallèle | Itératif | +| **Document** | Consensus arbitraire | Construction progressive | +| **Agents** | 4 | 3-7 (configurable) | +| **Convergence** | Vote pondéré (60%) | Naturelle (0 changements) | +| **Traçabilité** | Propositions individuelles | Historique complet | +| **Format** | JSON fixe | Flexible (MD/TXT) | +| **Qualité finale** | Bonne | Excellente | +| **Temps** | Rapide (1 appel) | Graduel (multiple tours) | + +### Fichiers Créés +- ✅ `backend/src/services/collaborativeOrchestrator.js` +- ✅ `backend/src/routes/collaborate.js` +- ✅ `frontend/src/stores/collaboration.js` +- ✅ `frontend/src/components/CollaborativeInput.vue` +- ✅ `frontend/src/components/CollaborativeSession.vue` +- ✅ `frontend/src/components/DocumentViewer.vue` +- ✅ `CHANGELOG.md` (ce fichier) + +### Fichiers Modifiés +- ✅ `backend/src/index.js` - Intégration orchestrateur +- ✅ `backend/src/db/schema.js` - Nouvelles tables +- ✅ `frontend/src/App.vue` - Mode selector et navigation +- ✅ `frontend/src/composables/useWebSocket.js` - Support sessionId +- ✅ `README.md` - Documentation + +### Tests à Faire +```bash +# Backend +npm start # Doit démarrer sans erreurs +curl http://localhost:3000/api/health + +# Frontend +npm run dev # Vite dev server +# Tester: http://localhost:5173 +``` + +### Prochaines Améliorations Possibles +- [ ] Support téléchargement fichiers contextuels (upload MD/TXT) +- [ ] Intégration Git (commit auto après convergence) +- [ ] Export PDF avec mise en page professionnelle +- [ ] Comparaison côte à côte des versions +- [ ] Statistiques détaillées par agent +- [ ] Webhook pour notifications externes +- [ ] Support collaboration multi-utilisateurs simultanés + +### Notes +- Système entièrement rétro-compatible : mode débat classique toujours disponible +- Performance : Chaque tour prend ~30-60s selon la longueur du document +- Coût API : Similaire au mode débat (n agents × 1 appel par tour) + +--- + +**Version** : 2.0.0 +**Date** : October 2024 +**Status** : Production Ready diff --git a/README.md b/README.md index 11522bf..5003d86 100644 --- a/README.md +++ b/README.md @@ -77,12 +77,29 @@ graph TD ## Fonctionnalités -### Système multi-agents IA -- **4 agents spécialisés** : Architecte logiciel, Ingénieur backend, Ingénieur frontend, Designer UI/UX +### Deux modes de collaboration + +#### 1. **Mode Débat Classique** (Original) +- **4 agents spécialisés** qui débattent en parallèle : Architecte logiciel, Ingénieur backend, Ingénieur frontend, Designer UI/UX - **Sélection automatique** des agents pertinents selon le contexte du projet - **Débat collaboratif** : Les agents échangent et négocient pour converger vers la meilleure solution - **Système de consensus** avec vote pondéré (l'architecte a une voix prépondérante) +- Résultats reçus simultanément + +#### 2. **Mode Conception Collaborative** (Nouveau 🚀) +- **7 agents spécialisés** : Architecte Lead, Ingénieur backend, Ingénieur frontend, Designer UI/UX, Ingénieur DevOps, Chef de produit, Spécialiste Sécurité +- **Création itérative** : L'Architecte Lead crée d'abord un document initial complet +- **Tours de table** : Chaque agent revoit séquentiellement le document et propose des améliorations +- **Document évolutif** : Le document s'améliore à chaque passage d'agent +- **Convergence naturelle** : Le processus continue jusqu'à ce qu'aucun agent ne propose de modifications +- **Traçabilité complète** : Historique complet de toutes les modifications avec justifications +- **Format flexible** : Support Markdown (.md) et texte brut (.txt) +- **Export facile** : Téléchargez le document final en format désiré + +### Système multi-agents IA - **Intégration Mistral AI** pour génération de réponses intelligentes et contextuelles +- **System prompts spécialisés** pour chaque rôle d'agent +- **Contexte intelligent** : Les agents voient l'historique et le document actuel ### Interface et visualisation - **Saisie de prompt** décrivant le projet souhaité @@ -133,6 +150,7 @@ npm run dev ## Flux utilisateur +### Mode Débat Classique ```mermaid sequenceDiagram participant User @@ -150,6 +168,64 @@ sequenceDiagram Orchestrator-->>User: Final structured response + diagram ``` +### Mode Conception Collaborative +```mermaid +sequenceDiagram + participant User + participant Orchestrator + participant Lead_Architect + participant Agents + User->>Orchestrator: Prompt project + format choice + Orchestrator->>Lead_Architect: Create initial document + Lead_Architect-->>Orchestrator: v1 Document + Orchestrator->>Agents: Review document Round 1 + Agents-->>Orchestrator: Modifications proposed + Orchestrator->>Agents: Review document Round 2 + Agents-->>Orchestrator: No changes OR more modifications + Orchestrator-->>User: Final document + full history +``` + +--- + +## Utilisation du Mode Conception Collaborative + +### Étapes + +1. **Sélectionner le mode** : Cliquez sur "Collaborative Design" depuis l'écran d'accueil +2. **Décrire le projet** : Entrez une description détaillée de votre projet logiciel +3. **Configurer** : + - **Format du document** : Choisissez entre Markdown (.md) ou Texte brut (.txt) + - **Nombre d'agents** : 3 (Quick), 5 (Balanced), ou 7 (Comprehensive) +4. **Lancer la session** : Cliquez sur "Start Collaborative Design Session" +5. **Suivre la progression** : + - Visualisez le document en évolution en temps réel + - Consultez la timeline pour voir qui a fait quoi + - Lancez les tours de table avec le bouton "Next Review Round" +6. **Convergence automatique** : Le système arrête automatiquement quand tous les agents sont satisfaits +7. **Télécharger** : Exportez le document final en format choisi + +### Exemple d'utilisation + +**Prompt utilisateur** : +``` +Je veux créer une plateforme de gestion de projets collaboratifs en temps réel, +avec support pour les équipes distribuées. Fonctionnalités clés: gestion des tâches, +communication temps réel, partage de fichiers, intégrations externes. +Besoin de scalabilité pour 10,000+ utilisateurs simultanés. +``` + +**Résultat** : +- **Round 1** : Lead Architect crée le document initial avec architecture générale +- **Round 2** : Backend Engineer revoit et ajoute détails API et base de données +- **Round 3** : Frontend Engineer améliore avec structure UI et composants +- **Round 4** : UI Designer ajoute guidelines UX et patterns +- **Round 5** : DevOps Engineer propose infrastructure et déploiement +- **Round 6** : Product Manager aligne avec besoins métier +- **Round 7** : Security Specialist ajoute mesures de sécurité +- **Convergence** : Plus aucune modification proposée ✓ + +Document final : Spécification architecturale complète et cohérente ! + --- ## Licence diff --git a/backend/src/db/schema.js b/backend/src/db/schema.js index 01dc122..63a2e55 100644 --- a/backend/src/db/schema.js +++ b/backend/src/db/schema.js @@ -17,7 +17,7 @@ const db = new Database(dbPath); // Enable foreign keys db.pragma('foreign_keys = ON'); -// Create debates table +// Create debates table (legacy, kept for backward compatibility) db.exec(` CREATE TABLE IF NOT EXISTS debates ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -27,7 +27,7 @@ db.exec(` ) `); -// Create responses table +// Create responses table (legacy, kept for backward compatibility) db.exec(` CREATE TABLE IF NOT EXISTS responses ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -39,6 +39,47 @@ db.exec(` ) `); +// Create collaborative sessions table +db.exec(` + CREATE TABLE IF NOT EXISTS collaborative_sessions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + initial_prompt TEXT NOT NULL, + document_format TEXT DEFAULT 'md' CHECK(document_format IN ('md', 'txt')), + status TEXT CHECK(status IN ('ongoing', 'completed', 'failed')) DEFAULT 'ongoing', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + completed_at TIMESTAMP, + final_document TEXT + ) +`); + +// Create document versions table +db.exec(` + CREATE TABLE IF NOT EXISTS document_versions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + session_id INTEGER NOT NULL, + version_number INTEGER NOT NULL, + content TEXT NOT NULL, + modified_by TEXT NOT NULL, + modification_reason TEXT, + round_number INTEGER NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (session_id) REFERENCES collaborative_sessions(id) ON DELETE CASCADE + ) +`); + +// Create document rounds table (track each round of agents) +db.exec(` + CREATE TABLE IF NOT EXISTS document_rounds ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + session_id INTEGER NOT NULL, + round_number INTEGER NOT NULL, + agents_in_round TEXT NOT NULL, + agents_made_changes TEXT DEFAULT '', + completed_at TIMESTAMP, + FOREIGN KEY (session_id) REFERENCES collaborative_sessions(id) ON DELETE CASCADE + ) +`); + console.log('Database initialized successfully'); export default db; diff --git a/backend/src/index.js b/backend/src/index.js index e6023c6..e548b67 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -8,7 +8,9 @@ import rateLimit from 'express-rate-limit'; import { parse } from 'url'; import db from './db/schema.js'; import debateRoutes from './routes/debate.js'; +import collaborateRoutes from './routes/collaborate.js'; import orchestrator from './services/orchestrator.js'; +import collaborativeOrchestrator from './services/collaborativeOrchestrator.js'; dotenv.config(); @@ -37,8 +39,10 @@ app.use('/api', limiter); wss.on('connection', (ws, req) => { const { query } = parse(req.url, true); const debateId = query.debateId ? parseInt(query.debateId) : null; + const sessionId = query.sessionId ? parseInt(query.sessionId) : null; - console.log('New WebSocket connection established', debateId ? `for debate ${debateId}` : ''); + console.log('New WebSocket connection established', + debateId ? `for debate ${debateId}` : sessionId ? `for session ${sessionId}` : ''); if (debateId) { orchestrator.registerWSClient(debateId, ws); @@ -50,6 +54,16 @@ wss.on('connection', (ws, req) => { })); } + if (sessionId) { + collaborativeOrchestrator.registerWSClient(sessionId, ws); + + ws.send(JSON.stringify({ + type: 'connected', + sessionId, + message: 'Connected to collaborative session updates' + })); + } + ws.on('message', (message) => { try { const data = JSON.parse(message.toString()); @@ -63,6 +77,15 @@ wss.on('connection', (ws, req) => { debateId: data.debateId })); } + + // Handle subscribe to collaborative session + if (data.type === 'subscribe' && data.sessionId) { + collaborativeOrchestrator.registerWSClient(parseInt(data.sessionId), ws); + ws.send(JSON.stringify({ + type: 'subscribed', + sessionId: data.sessionId + })); + } } catch (error) { console.error('WebSocket message error:', error); } @@ -72,6 +95,9 @@ wss.on('connection', (ws, req) => { if (debateId) { orchestrator.unregisterWSClient(debateId, ws); } + if (sessionId) { + collaborativeOrchestrator.unregisterWSClient(sessionId, ws); + } console.log('WebSocket connection closed'); }); }); @@ -82,6 +108,7 @@ app.get('/api/health', (req, res) => { }); app.use('/api/debate', debateRoutes); +app.use('/api/collaborate', collaborateRoutes); // Start server server.listen(PORT, () => { diff --git a/backend/src/routes/collaborate.js b/backend/src/routes/collaborate.js new file mode 100644 index 0000000..1d8c325 --- /dev/null +++ b/backend/src/routes/collaborate.js @@ -0,0 +1,264 @@ +import express from 'express'; +import collaborativeOrchestrator from '../services/collaborativeOrchestrator.js'; + +const router = express.Router(); + +/** + * POST /api/collaborate + * Create a new collaborative session + */ +router.post('/', async (req, res) => { + try { + const { prompt, documentFormat = 'md', agentCount = 7 } = req.body; + + if (!prompt || prompt.trim().length === 0) { + return res.status(400).json({ error: 'Prompt is required' }); + } + + if (!['md', 'txt'].includes(documentFormat)) { + return res.status(400).json({ error: 'Document format must be "md" or "txt"' }); + } + + const sessionId = collaborativeOrchestrator.createSession( + prompt, + documentFormat, + Math.min(agentCount, 7) + ); + + const session = collaborativeOrchestrator.getSessionDetails(sessionId); + + res.json({ + sessionId, + prompt, + documentFormat, + status: 'created', + agents: session.agents.map(a => a.role), + message: 'Collaborative session created. Start the session to begin collaboration.' + }); + + } catch (error) { + console.error('Error creating collaborative session:', error); + res.status(500).json({ error: 'Failed to create collaborative session' }); + } +}); + +/** + * POST /api/collaborate/:id/start + * Start the collaborative session (Lead Architect creates initial document) + */ +router.post('/:id/start', async (req, res) => { + try { + const sessionId = parseInt(req.params.id); + const session = collaborativeOrchestrator.getSession(sessionId); + + if (!session) { + return res.status(404).json({ error: 'Session not found' }); + } + + if (session.status !== 'ongoing') { + return res.status(400).json({ error: 'Session is not in ongoing status' }); + } + + // Start the session asynchronously + res.json({ + sessionId, + status: 'starting', + message: 'Session is starting. Initial document is being created...' + }); + + // Start asynchronously without waiting + collaborativeOrchestrator.startCollaborativeSession(sessionId).catch(error => { + console.error('Error starting collaborative session:', error); + }); + + } catch (error) { + console.error('Error starting collaborative session:', error); + res.status(500).json({ error: 'Failed to start collaborative session' }); + } +}); + +/** + * POST /api/collaborate/:id/round + * Run the next round of reviews + */ +router.post('/:id/round', async (req, res) => { + try { + const sessionId = parseInt(req.params.id); + const session = collaborativeOrchestrator.getSession(sessionId); + + if (!session) { + return res.status(404).json({ error: 'Session not found' }); + } + + if (session.status !== 'ongoing') { + return res.status(400).json({ error: 'Session is not in ongoing status' }); + } + + const activeSession = collaborativeOrchestrator.activeSessions.get(sessionId); + if (!activeSession?.started) { + return res.status(400).json({ error: 'Session has not been started yet' }); + } + + // Run round asynchronously + res.json({ + sessionId, + roundNumber: activeSession.currentRound + 1, + status: 'running', + message: 'Review round in progress...' + }); + + // Run asynchronously without waiting + collaborativeOrchestrator.runRound(sessionId).catch(error => { + console.error('Error running round:', error); + collaborativeOrchestrator.broadcast(sessionId, { + type: 'round_error', + sessionId, + error: error.message + }); + }); + + } catch (error) { + console.error('Error running round:', error); + res.status(500).json({ error: 'Failed to run round' }); + } +}); + +/** + * GET /api/collaborate/:id + * Get session details with full history and current document + */ +router.get('/:id', (req, res) => { + try { + const sessionId = parseInt(req.params.id); + const session = collaborativeOrchestrator.getSessionDetails(sessionId); + + if (!session) { + return res.status(404).json({ error: 'Session not found' }); + } + + res.json({ + sessionId: session.id, + initialPrompt: session.initial_prompt, + documentFormat: session.document_format, + status: session.status, + createdAt: session.created_at, + completedAt: session.completed_at, + currentRound: session.currentRound, + agents: session.agents.map(a => a.role), + currentDocument: session.currentDocument, + documentVersionCount: session.versions.length, + conversationHistory: session.conversationHistory, + versions: session.versions.map(v => ({ + versionNumber: v.version_number, + modifiedBy: v.modified_by, + modificationReason: v.modification_reason, + roundNumber: v.round_number, + createdAt: v.created_at + })) + }); + + } catch (error) { + console.error('Error fetching session details:', error); + res.status(500).json({ error: 'Failed to fetch session details' }); + } +}); + +/** + * GET /api/collaborate/:id/document + * Get the current document + */ +router.get('/:id/document', (req, res) => { + try { + const sessionId = parseInt(req.params.id); + const session = collaborativeOrchestrator.getSession(sessionId); + + if (!session) { + return res.status(404).json({ error: 'Session not found' }); + } + + const activeSession = collaborativeOrchestrator.activeSessions.get(sessionId); + const currentDocument = activeSession?.currentDocument || ''; + + // Determine content type based on format + const contentType = session.document_format === 'md' + ? 'text/markdown; charset=utf-8' + : 'text/plain; charset=utf-8'; + + res.set('Content-Type', contentType); + res.send(currentDocument); + + } catch (error) { + console.error('Error fetching document:', error); + res.status(500).json({ error: 'Failed to fetch document' }); + } +}); + +/** + * GET /api/collaborate/:id/versions/:versionNumber + * Get a specific document version + */ +router.get('/:id/versions/:versionNumber', (req, res) => { + try { + const sessionId = parseInt(req.params.id); + const versionNumber = parseInt(req.params.versionNumber); + + const session = collaborativeOrchestrator.getSession(sessionId); + if (!session) { + return res.status(404).json({ error: 'Session not found' }); + } + + const versions = collaborativeOrchestrator.getDocumentVersions(sessionId); + const version = versions.find(v => v.version_number === versionNumber); + + if (!version) { + return res.status(404).json({ error: 'Version not found' }); + } + + const contentType = session.document_format === 'md' + ? 'text/markdown; charset=utf-8' + : 'text/plain; charset=utf-8'; + + res.set('Content-Type', contentType); + res.json({ + versionNumber: version.version_number, + modifiedBy: version.modified_by, + modificationReason: version.modification_reason, + roundNumber: version.round_number, + createdAt: version.created_at, + content: version.content + }); + + } catch (error) { + console.error('Error fetching version:', error); + res.status(500).json({ error: 'Failed to fetch version' }); + } +}); + +/** + * POST /api/collaborate/:id/complete + * Complete the collaborative session + */ +router.post('/:id/complete', (req, res) => { + try { + const sessionId = parseInt(req.params.id); + const session = collaborativeOrchestrator.getSession(sessionId); + + if (!session) { + return res.status(404).json({ error: 'Session not found' }); + } + + collaborativeOrchestrator.completeSession(sessionId); + + res.json({ + sessionId, + status: 'completed', + message: 'Session completed successfully' + }); + + } catch (error) { + console.error('Error completing session:', error); + res.status(500).json({ error: 'Failed to complete session' }); + } +}); + +export default router; diff --git a/backend/src/services/collaborativeOrchestrator.js b/backend/src/services/collaborativeOrchestrator.js new file mode 100644 index 0000000..218e178 --- /dev/null +++ b/backend/src/services/collaborativeOrchestrator.js @@ -0,0 +1,469 @@ +import db from '../db/schema.js'; +import { generateAgentResponse } from './mistralClient.js'; + +class CollaborativeOrchestrator { + constructor() { + this.activeSessions = new Map(); + this.wsClients = new Map(); // sessionId -> Set of WebSocket clients + + // Define collaborative agents with specialized roles + this.agents = [ + { role: 'lead_architect', isLead: true }, + { role: 'backend_engineer', isLead: false }, + { role: 'frontend_engineer', isLead: false }, + { role: 'ui_designer', isLead: false }, + { role: 'devops_engineer', isLead: false }, + { role: 'product_manager', isLead: false }, + { role: 'security_specialist', isLead: false } + ]; + } + + /** + * Register WebSocket client for a session + */ + registerWSClient(sessionId, ws) { + if (!this.wsClients.has(sessionId)) { + this.wsClients.set(sessionId, new Set()); + } + this.wsClients.get(sessionId).add(ws); + } + + /** + * Unregister WebSocket client + */ + unregisterWSClient(sessionId, ws) { + if (this.wsClients.has(sessionId)) { + this.wsClients.get(sessionId).delete(ws); + } + } + + /** + * Broadcast message to all clients watching a session + */ + broadcast(sessionId, message) { + if (this.wsClients.has(sessionId)) { + const data = JSON.stringify(message); + this.wsClients.get(sessionId).forEach(ws => { + if (ws.readyState === 1) { // OPEN + ws.send(data); + } + }); + } + } + + /** + * Create a new collaborative session + */ + createSession(initialPrompt, documentFormat = 'md', agentCount = 7) { + const stmt = db.prepare( + 'INSERT INTO collaborative_sessions (initial_prompt, document_format, status) VALUES (?, ?, ?)' + ); + const result = stmt.run(initialPrompt, documentFormat, 'ongoing'); + const sessionId = result.lastInsertRowid; + + // Select the agents to use + const selectedAgents = this.agents.slice(0, Math.min(agentCount, this.agents.length)); + + this.activeSessions.set(sessionId, { + id: sessionId, + initialPrompt, + documentFormat, + agents: selectedAgents, + currentRound: 0, + currentDocument: null, + versionNumber: 0, + conversationHistory: [], + started: false + }); + + return sessionId; + } + + /** + * Get session by ID + */ + getSession(sessionId) { + const stmt = db.prepare('SELECT * FROM collaborative_sessions WHERE id = ?'); + return stmt.get(sessionId); + } + + /** + * Get all versions of a document + */ + getDocumentVersions(sessionId) { + const stmt = db.prepare( + `SELECT * FROM document_versions + WHERE session_id = ? + ORDER BY version_number ASC` + ); + return stmt.all(sessionId); + } + + /** + * Get latest document version + */ + getLatestDocument(sessionId) { + const stmt = db.prepare( + `SELECT * FROM document_versions + WHERE session_id = ? + ORDER BY version_number DESC + LIMIT 1` + ); + return stmt.get(sessionId); + } + + /** + * Save a document version + */ + saveDocumentVersion(sessionId, content, modifiedBy, reason, roundNumber) { + // Get current version number + const lastVersion = this.getLatestDocument(sessionId); + const versionNumber = (lastVersion?.version_number || 0) + 1; + + const stmt = db.prepare( + `INSERT INTO document_versions + (session_id, version_number, content, modified_by, modification_reason, round_number) + VALUES (?, ?, ?, ?, ?, ?)` + ); + + const result = stmt.run(sessionId, versionNumber, content, modifiedBy, reason, roundNumber); + return result.lastInsertRowid; + } + + /** + * Start the collaborative session - Lead Architect creates first version + */ + async startCollaborativeSession(sessionId) { + try { + const session = this.getSession(sessionId); + if (!session) { + throw new Error('Session not found'); + } + + const activeSession = this.activeSessions.get(sessionId); + if (!activeSession) { + throw new Error('Active session not found'); + } + + activeSession.started = true; + + // Broadcast session start + this.broadcast(sessionId, { + type: 'session_start', + sessionId, + message: 'Collaborative session started. Lead Architect is creating initial document...' + }); + + // Get Lead Architect (first agent) + const leadArchitect = activeSession.agents.find(a => a.isLead); + + // Generate initial document + const response = await generateAgentResponse( + leadArchitect.role, + `${session.initial_prompt}\n\nYou are the Lead Architect. Create the INITIAL version of a comprehensive project document in ${session.document_format} format. + +This document will be reviewed and modified by other team members (Backend Engineer, Frontend Engineer, UI Designer, DevOps Engineer, Product Manager, Security Specialist). + +Create a structured, complete document that outlines: +- Project overview and goals +- Architecture overview +- Technology stack decisions +- Project structure +- Key features +- Non-functional requirements +- Timeline and phases + +Output ONLY the raw document content in ${session.document_format} format, nothing else.` + ); + + // Extract document from response + let documentContent = response.proposal || response; + if (typeof documentContent === 'object') { + documentContent = JSON.stringify(documentContent, null, 2); + } + + // Save as first version + this.saveDocumentVersion( + sessionId, + documentContent, + leadArchitect.role, + 'Initial document creation', + 1 + ); + + // Update in-memory session + activeSession.currentDocument = documentContent; + activeSession.versionNumber = 1; + activeSession.currentRound = 1; + activeSession.conversationHistory.push({ + agent: leadArchitect.role, + action: 'created_initial_document', + documentVersion: 1 + }); + + this.broadcast(sessionId, { + type: 'initial_document_created', + sessionId, + agent: leadArchitect.role, + documentVersion: 1, + document: documentContent, + message: `Lead Architect (${leadArchitect.role}) created initial document. Starting review rounds...` + }); + + return { + sessionId, + documentVersion: 1, + document: documentContent, + agents: activeSession.agents + }; + + } catch (error) { + console.error('Error starting collaborative session:', error); + this.failSession(sessionId); + + this.broadcast(sessionId, { + type: 'session_error', + sessionId, + error: error.message + }); + + throw error; + } + } + + /** + * Run one round of review (each agent reviews and potentially modifies) + */ + async runRound(sessionId) { + try { + const session = this.getSession(sessionId); + if (!session) { + throw new Error('Session not found'); + } + + const activeSession = this.activeSessions.get(sessionId); + if (!activeSession) { + throw new Error('Active session not found'); + } + + if (!activeSession.currentDocument) { + throw new Error('No document to review'); + } + + activeSession.currentRound += 1; + const roundNumber = activeSession.currentRound; + + this.broadcast(sessionId, { + type: 'round_start', + sessionId, + roundNumber, + message: `Starting review round ${roundNumber}. Agents will review and modify the document...` + }); + + const agentsMadeChanges = []; + const roundAgents = activeSession.agents.map(a => a.role).join(','); + + // Process each agent sequentially + for (const agent of activeSession.agents) { + this.broadcast(sessionId, { + type: 'agent_reviewing', + sessionId, + agent: agent.role, + roundNumber, + message: `${agent.role} is reviewing the document...` + }); + + // Call agent to review and potentially modify document + const response = await generateAgentResponse( + agent.role, + `${session.initial_prompt}\n\n +CURRENT DOCUMENT (${session.document_format} format): +\`\`\`${session.document_format} +${activeSession.currentDocument} +\`\`\` + +You are the ${agent.role}. Review this document from your perspective. Your task is to: +1. Read and understand the entire document +2. Identify areas that need improvement or modifications from your expertise area +3. Provide improvements, additions, or modifications + +IMPORTANT: +- If you decide to modify the document, output ONLY the complete modified document in ${session.document_format} format +- If the document is already excellent and needs no changes, output: NO_CHANGES +- Do not include explanations, just the document or NO_CHANGES + +Modification focus for ${agent.role}: +${this.getAgentFocusArea(agent.role)}` + ); + + let responseText = response.proposal || response; + if (typeof responseText === 'object') { + responseText = JSON.stringify(responseText, null, 2); + } + + // Check if agent made changes + if (responseText.trim() !== 'NO_CHANGES') { + // Calculate diff + const diff = this.calculateDiff(activeSession.currentDocument, responseText); + + // Save new version + this.saveDocumentVersion( + sessionId, + responseText, + agent.role, + diff.summary, + roundNumber + ); + + activeSession.currentDocument = responseText; + activeSession.versionNumber += 1; + agentsMadeChanges.push(agent.role); + + this.broadcast(sessionId, { + type: 'document_modified', + sessionId, + agent: agent.role, + roundNumber, + documentVersion: activeSession.versionNumber, + document: responseText, + changeSummary: diff.summary, + message: `${agent.role} modified the document` + }); + } else { + this.broadcast(sessionId, { + type: 'agent_no_changes', + sessionId, + agent: agent.role, + roundNumber, + message: `${agent.role} reviewed the document and found no changes needed` + }); + } + } + + // Save round completion + const roundStmt = db.prepare( + `INSERT INTO document_rounds + (session_id, round_number, agents_in_round, agents_made_changes, completed_at) + VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)` + ); + roundStmt.run( + sessionId, + roundNumber, + roundAgents, + agentsMadeChanges.join(',') + ); + + const conversationEntry = { + roundNumber, + agentsMadeChanges: agentsMadeChanges.length > 0 ? agentsMadeChanges : 'none' + }; + + activeSession.conversationHistory.push(conversationEntry); + + // Check for convergence + const hasConverged = agentsMadeChanges.length === 0; + + this.broadcast(sessionId, { + type: 'round_complete', + sessionId, + roundNumber, + agentsMadeChanges: agentsMadeChanges.length, + agentsWhoModified: agentsMadeChanges, + hasConverged, + message: hasConverged + ? 'Convergence reached! No more changes needed.' + : `Round ${roundNumber} complete. ${agentsMadeChanges.length} agent(s) made changes.` + }); + + return { + roundNumber, + agentsMadeChanges, + hasConverged, + documentVersion: activeSession.versionNumber + }; + + } catch (error) { + console.error('Error running round:', error); + throw error; + } + } + + /** + * Get focus area for each agent + */ + getAgentFocusArea(agentRole) { + const focusAreas = { + lead_architect: 'High-level architecture, technology stack, system design, scalability', + backend_engineer: 'APIs, databases, backend services, data models, performance, security', + frontend_engineer: 'UI components, state management, performance, user interactions, frameworks', + ui_designer: 'User experience, accessibility, visual design, UI patterns, usability', + devops_engineer: 'Deployment, infrastructure, CI/CD, monitoring, containers, scalability', + product_manager: 'Features, user needs, roadmap, priorities, market fit, business goals', + security_specialist: 'Security architecture, data protection, authentication, compliance' + }; + return focusAreas[agentRole] || 'General improvements'; + } + + /** + * Calculate diff between two document versions (simplified) + */ + calculateDiff(oldContent, newContent) { + const oldLines = oldContent.split('\n'); + const newLines = newContent.split('\n'); + + const linesAdded = Math.max(0, newLines.length - oldLines.length); + const linesRemoved = Math.max(0, oldLines.length - newLines.length); + const changes = Math.abs(linesAdded) + Math.abs(linesRemoved); + + const summary = `Modified document: +${linesAdded} lines, -${linesRemoved} lines (${changes} total changes)`; + + return { summary, linesAdded, linesRemoved }; + } + + /** + * Complete a session + */ + completeSession(sessionId) { + const session = this.activeSessions.get(sessionId); + const document = session?.currentDocument || ''; + + const stmt = db.prepare( + 'UPDATE collaborative_sessions SET status = ?, completed_at = CURRENT_TIMESTAMP, final_document = ? WHERE id = ?' + ); + stmt.run('completed', document, sessionId); + + this.activeSessions.delete(sessionId); + } + + /** + * Fail a session + */ + failSession(sessionId) { + const stmt = db.prepare( + 'UPDATE collaborative_sessions SET status = ? WHERE id = ?' + ); + stmt.run('failed', sessionId); + + this.activeSessions.delete(sessionId); + } + + /** + * Get session details with full history + */ + getSessionDetails(sessionId) { + const session = this.getSession(sessionId); + const versions = this.getDocumentVersions(sessionId); + const activeSession = this.activeSessions.get(sessionId); + + return { + ...session, + versions, + currentRound: activeSession?.currentRound || 0, + currentDocument: activeSession?.currentDocument || null, + agents: activeSession?.agents || [], + conversationHistory: activeSession?.conversationHistory || [] + }; + } +} + +export default new CollaborativeOrchestrator(); diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 6138523..c26e115 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,37 +1,117 @@