Clean up: Remove emojis, unused APIs and files

Removed:
- All emojis from code and UI (using minimal styling instead)
- Classic debate mode completely (orchestrator.js, debate.js routes)
- Debate components (DebateThread.vue, PromptInput.vue)
- Debate store (debate.js)
- Unused database tables (debates, responses)

Simplified:
- App.vue: Single direct flow (input -> session)
- Backend: Only /api/collaborate routes
- NetworkStatus: Simple dot indicator with colors
- README.md: Consolidated documentation
- UX: Cleaner, more minimal interface

Cleanup focused on keeping only collaborative design system
Files removed: 6 | Files modified: 6

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Augustin ROUX 2025-10-17 17:22:49 +02:00
parent f20c0b4996
commit 08056ce9fd
12 changed files with 74 additions and 1084 deletions

121
README.md
View File

@ -2,7 +2,7 @@
## Description
Project Agora est une plateforme web open-source permettant à plusieurs intelligences artificielles de **collaborer entre elles** pour concevoir la structure complète d'un projet logiciel à partir d'un **prompt utilisateur** ou d'un **document explicatif**.
Project Agora est une plateforme web permettant à plusieurs intelligences artificielles de **collaborer** pour concevoir une architecture logicielle complète à partir d'une **description de projet** et optionnellement d'une **documentation contexte**.
L'objectif est de produire :
@ -77,24 +77,17 @@ graph TD
## Fonctionnalités
### Deux modes de collaboration
### Conception Collaborative par IA
#### 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é
- **7 agents spécialisés** : Architecte, Backend Engineer, Frontend Engineer, UI Designer, DevOps Engineer, Product Manager, Security Specialist
- **Création itérative** : Lead Architect crée le document initial
- **Tours de table** : Chaque agent revoit et propose des améliorations
- **Document évolutif** : Amélioration progressive à chaque passage
- **Convergence naturelle** : Arrêt automatique quand tous les agents sont satisfaits
- **Traçabilité complète** : Historique de toutes les modifications
- **Format Markdown** : Output toujours en Markdown (.md)
- **Context files** : Support d'upload de fichiers MD/TXT en entrée (optionnel)
- **Network monitoring** : Indicateur temps réel de latence et qualité réseau
### Système multi-agents IA
- **Intégration Mistral AI** pour génération de réponses intelligentes et contextuelles
@ -148,83 +141,57 @@ npm run dev
---
## Flux utilisateur
## Flux Utilisateur
### Mode Débat Classique
```mermaid
sequenceDiagram
participant User
participant Orchestrator
participant AI_Architect
participant AI_Designer
participant AI_Engineer
User->>Orchestrator: Prompt project
Orchestrator->>AI_Architect: Generate approach
Orchestrator->>AI_Designer: Discuss UI/UX
Orchestrator->>AI_Engineer: Suggest modules
AI_Architect-->>Orchestrator: Architecture proposal
AI_Designer-->>Orchestrator: Interface ideas
AI_Engineer-->>Orchestrator: Technical design
Orchestrator-->>User: Final structured response + diagram
```
### Mode Conception Collaborative
```mermaid
sequenceDiagram
participant User
participant Orchestrator
participant Lead_Architect
participant Backend
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
User->>Backend: Project description + optional context file
Backend->>Agents: Lead Architect creates initial document
Agents-->>Backend: Document v1
Backend->>Agents: Round 1: Review and improve
Agents-->>Backend: Updates proposed
Backend->>Agents: Round 2-N: Review updated document
Agents-->>Backend: More updates OR no changes
Backend-->>User: Final Markdown document + history
```
---
## Utilisation du Mode Conception Collaborative
## Utilisation
### É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
1. **Décrire le projet** : Entrez une description détaillée
2. **Ajouter un contexte** (optionnel) : Upload un fichier MD ou TXT
3. **Sélectionner** : Nombre d'agents (3, 5, ou 7)
4. **Lancer** : Start Design Session
5. **Suivre** :
- Visualisez le document en temps réel
- Consultez la timeline
- Lancez les tours avec "Next Review Round"
6. **Résultat** : Document Markdown téléchargeable
### Exemple d'utilisation
### Exemple
**Prompt utilisateur** :
Entrée:
```
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.
Plateforme gestion projets temps réel, équipes distribuées,
gestion tâches, communication, partage fichiers, 10K+ users
```
**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 ✓
Processus:
- Round 1: Lead Architect - Architecture générale
- Round 2: Backend - APIs et base de données
- Round 3: Frontend - Structure UI
- Round 4: UI Designer - Guidelines UX
- Round 5: DevOps - Infrastructure
- Round 6: Product Manager - Besoins métier
- Round 7: Security - Mesures sécurité
Document final : Spécification architecturale complète et cohérente !
Sortie: Spécification architecturale Markdown complète
---

View File

@ -17,28 +17,6 @@ const db = new Database(dbPath);
// Enable foreign keys
db.pragma('foreign_keys = ON');
// Create debates table (legacy, kept for backward compatibility)
db.exec(`
CREATE TABLE IF NOT EXISTS debates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
prompt TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status TEXT CHECK(status IN ('ongoing', 'completed', 'failed')) DEFAULT 'ongoing'
)
`);
// Create responses table (legacy, kept for backward compatibility)
db.exec(`
CREATE TABLE IF NOT EXISTS responses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
debate_id INTEGER NOT NULL,
agent_role TEXT NOT NULL,
content TEXT NOT NULL,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (debate_id) REFERENCES debates(id) ON DELETE CASCADE
)
`);
// Create collaborative sessions table
db.exec(`
CREATE TABLE IF NOT EXISTS collaborative_sessions (

View File

@ -7,9 +7,7 @@ import { WebSocketServer } from 'ws';
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();
@ -38,21 +36,9 @@ app.use('/api', limiter);
// WebSocket connection handling
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}` : sessionId ? `for session ${sessionId}` : '');
if (debateId) {
orchestrator.registerWSClient(debateId, ws);
ws.send(JSON.stringify({
type: 'connected',
debateId,
message: 'Connected to debate updates'
}));
}
console.log('WebSocket connection', sessionId ? `session ${sessionId}` : 'established');
if (sessionId) {
collaborativeOrchestrator.registerWSClient(sessionId, ws);
@ -60,7 +46,7 @@ wss.on('connection', (ws, req) => {
ws.send(JSON.stringify({
type: 'connected',
sessionId,
message: 'Connected to collaborative session updates'
message: 'Connected to session'
}));
}
@ -69,16 +55,6 @@ wss.on('connection', (ws, req) => {
const data = JSON.parse(message.toString());
console.log('Received:', data);
// Handle subscribe to debate
if (data.type === 'subscribe' && data.debateId) {
orchestrator.registerWSClient(parseInt(data.debateId), ws);
ws.send(JSON.stringify({
type: 'subscribed',
debateId: data.debateId
}));
}
// Handle subscribe to collaborative session
if (data.type === 'subscribe' && data.sessionId) {
collaborativeOrchestrator.registerWSClient(parseInt(data.sessionId), ws);
ws.send(JSON.stringify({
@ -92,22 +68,18 @@ wss.on('connection', (ws, req) => {
});
ws.on('close', () => {
if (debateId) {
orchestrator.unregisterWSClient(debateId, ws);
}
if (sessionId) {
collaborativeOrchestrator.unregisterWSClient(sessionId, ws);
}
console.log('WebSocket connection closed');
console.log('WebSocket closed');
});
});
// Routes
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', message: 'Agora AI Backend is running' });
res.json({ status: 'ok' });
});
app.use('/api/debate', debateRoutes);
app.use('/api/collaborate', collaborateRoutes);
// Start server

View File

@ -1,95 +0,0 @@
import express from 'express';
import orchestrator from '../services/orchestrator.js';
const router = express.Router();
/**
* POST /api/debate
* Create a new debate and start AI discussion
*/
router.post('/', async (req, res) => {
try {
const { prompt } = req.body;
if (!prompt || prompt.trim().length === 0) {
return res.status(400).json({ error: 'Prompt is required' });
}
const debateId = orchestrator.createDebate(prompt);
const agents = orchestrator.selectAgents(prompt);
// Send immediate response
res.json({
debateId,
prompt,
agents,
status: 'ongoing'
});
// Start debate asynchronously (don't wait for response)
orchestrator.startDebate(debateId, agents).catch(error => {
console.error('Debate failed:', error);
});
} catch (error) {
console.error('Error creating debate:', error);
res.status(500).json({ error: 'Failed to create debate' });
}
});
/**
* GET /api/debate/:id
* Get debate details and responses
*/
router.get('/:id', (req, res) => {
try {
const debateId = parseInt(req.params.id);
const debate = orchestrator.getDebate(debateId);
if (!debate) {
return res.status(404).json({ error: 'Debate not found' });
}
const responses = orchestrator.getDebateResponses(debateId);
res.json({
...debate,
responses: responses.map(r => ({
...r,
content: JSON.parse(r.content)
}))
});
} catch (error) {
console.error('Error fetching debate:', error);
res.status(500).json({ error: 'Failed to fetch debate' });
}
});
/**
* POST /api/debate/:id/response
* Add a response to a debate
*/
router.post('/:id/response', (req, res) => {
try {
const debateId = parseInt(req.params.id);
const { agentRole, content } = req.body;
if (!agentRole || !content) {
return res.status(400).json({ error: 'Agent role and content are required' });
}
const responseId = orchestrator.addResponse(debateId, agentRole, content);
res.json({
responseId,
debateId,
agentRole,
content
});
} catch (error) {
console.error('Error adding response:', error);
res.status(500).json({ error: 'Failed to add response' });
}
});
export default router;

View File

@ -1,236 +0,0 @@
import db from '../db/schema.js';
import { generateMultiAgentResponses } from './mistralClient.js';
class Orchestrator {
constructor() {
this.activeDebates = new Map();
this.wsClients = new Map(); // debateId -> Set of WebSocket clients
}
/**
* Register WebSocket client for a debate
*/
registerWSClient(debateId, ws) {
if (!this.wsClients.has(debateId)) {
this.wsClients.set(debateId, new Set());
}
this.wsClients.get(debateId).add(ws);
}
/**
* Unregister WebSocket client
*/
unregisterWSClient(debateId, ws) {
if (this.wsClients.has(debateId)) {
this.wsClients.get(debateId).delete(ws);
}
}
/**
* Broadcast message to all clients watching a debate
*/
broadcast(debateId, message) {
if (this.wsClients.has(debateId)) {
const data = JSON.stringify(message);
this.wsClients.get(debateId).forEach(ws => {
if (ws.readyState === 1) { // OPEN
ws.send(data);
}
});
}
}
/**
* Create a new debate
*/
createDebate(prompt) {
const stmt = db.prepare('INSERT INTO debates (prompt, status) VALUES (?, ?)');
const result = stmt.run(prompt, 'ongoing');
const debateId = result.lastInsertRowid;
this.activeDebates.set(debateId, {
id: debateId,
prompt,
responses: [],
startTime: Date.now()
});
return debateId;
}
/**
* Get debate by ID
*/
getDebate(debateId) {
const stmt = db.prepare('SELECT * FROM debates WHERE id = ?');
return stmt.get(debateId);
}
/**
* Get all responses for a debate
*/
getDebateResponses(debateId) {
const stmt = db.prepare('SELECT * FROM responses WHERE debate_id = ? ORDER BY timestamp ASC');
return stmt.all(debateId);
}
/**
* Add a response to a debate
*/
addResponse(debateId, agentRole, content) {
const stmt = db.prepare(
'INSERT INTO responses (debate_id, agent_role, content) VALUES (?, ?, ?)'
);
const result = stmt.run(debateId, agentRole, JSON.stringify(content));
if (this.activeDebates.has(debateId)) {
this.activeDebates.get(debateId).responses.push({
agentRole,
content,
timestamp: new Date()
});
}
return result.lastInsertRowid;
}
/**
* Complete a debate
*/
completeDebate(debateId) {
const stmt = db.prepare('UPDATE debates SET status = ? WHERE id = ?');
stmt.run('completed', debateId);
this.activeDebates.delete(debateId);
}
/**
* Fail a debate
*/
failDebate(debateId) {
const stmt = db.prepare('UPDATE debates SET status = ? WHERE id = ?');
stmt.run('failed', debateId);
this.activeDebates.delete(debateId);
}
/**
* Select relevant agents based on prompt analysis
*/
selectAgents(prompt) {
const agents = ['architect'];
const lowerPrompt = prompt.toLowerCase();
// Analyze prompt for relevant expertise
if (lowerPrompt.includes('api') || lowerPrompt.includes('backend') || lowerPrompt.includes('database')) {
agents.push('backend_engineer');
}
if (lowerPrompt.includes('ui') || lowerPrompt.includes('frontend') || lowerPrompt.includes('interface')) {
agents.push('frontend_engineer');
}
if (lowerPrompt.includes('design') || lowerPrompt.includes('ux') || lowerPrompt.includes('user')) {
agents.push('designer');
}
// Always include at least architect and one engineer
if (agents.length === 1) {
agents.push('backend_engineer', 'frontend_engineer');
}
return agents;
}
/**
* Start AI debate - trigger agents and collect responses
*/
async startDebate(debateId, agents) {
try {
const debate = this.getDebate(debateId);
if (!debate) {
throw new Error('Debate not found');
}
const prompt = debate.prompt;
const context = this.getDebateResponses(debateId);
// Broadcast debate start
this.broadcast(debateId, {
type: 'debate_start',
debateId,
agents,
message: 'AI agents are analyzing your project...'
});
// Generate responses from all agents in parallel
const agentResponses = await generateMultiAgentResponses(agents, prompt, context);
// Store responses and broadcast each one
for (const { agent, response } of agentResponses) {
const responseId = this.addResponse(debateId, agent, response);
this.broadcast(debateId, {
type: 'agent_response',
debateId,
responseId,
agent,
response
});
}
// Calculate consensus
const consensus = this.calculateConsensus(agentResponses);
// Complete debate
this.completeDebate(debateId);
this.broadcast(debateId, {
type: 'debate_complete',
debateId,
consensus,
message: 'Debate completed successfully'
});
return {
responses: agentResponses,
consensus
};
} catch (error) {
console.error('Error in debate:', error);
this.failDebate(debateId);
this.broadcast(debateId, {
type: 'debate_error',
debateId,
error: error.message
});
throw error;
}
}
/**
* Calculate consensus from agent responses
*/
calculateConsensus(agentResponses) {
const proposals = agentResponses.map(({ agent, response }) => ({
agent,
proposal: response.proposal,
confidence: response.confidence || 0.5
}));
// Weight by confidence and architect gets 1.5x weight
const totalWeight = proposals.reduce((sum, p) => {
const weight = p.agent === 'architect' ? 1.5 : 1.0;
return sum + (p.confidence * weight);
}, 0);
const avgConfidence = totalWeight / proposals.length;
return {
proposals,
averageConfidence: avgConfidence,
status: avgConfidence >= 0.6 ? 'consensus_reached' : 'needs_discussion'
};
}
}
export default new Orchestrator();

View File

@ -33,7 +33,7 @@ function startNewCollaboration() {
<!-- Session Mode -->
<div v-else>
<button @click="startNewCollaboration" class="new-session-btn">
+ New Session
New Session
</button>
<CollaborativeSession

View File

@ -85,10 +85,10 @@ const removeFile = () => {
<div class="collaborative-input">
<div class="container">
<header class="header">
<h1>🎯 AI Architecture Design</h1>
<h1>AI Architecture Design</h1>
<p class="subtitle">
Describe your software project. Multiple AI specialists will collaboratively
design the perfect architecture through iterative refinement.
Describe your software project. AI specialists collaboratively design
the architecture through iterative refinement.
</p>
</header>
@ -103,11 +103,11 @@ const removeFile = () => {
id="prompt"
v-model="prompt"
@keydown="handleKeydown"
placeholder="Example: I want to build a real-time collaborative document editing platform with support for multiple users, version history, and commenting..."
placeholder="Example: Real-time collaborative document editing platform with multiple users, version history, and commenting..."
class="textarea"
rows="8"
></textarea>
<p class="hint">💡 The more detailed your description, the better the AI collaboration.</p>
<p class="hint">The more detailed your description, the better the AI collaboration.</p>
</div>
<!-- Optional Context File -->
@ -126,18 +126,18 @@ const removeFile = () => {
class="file-input"
/>
<label for="contextFile" class="file-label">
📎 Choose a file (.md or .txt)
Choose a file (.md or .txt)
</label>
</div>
<div v-if="contextFile" class="file-selected">
<span class="file-name"> {{ contextFile.name }}</span>
<span class="file-name">{{ contextFile.name }}</span>
<button
type="button"
@click="removeFile"
class="remove-btn"
>
Remove
</button>
</div>
@ -159,7 +159,7 @@ const removeFile = () => {
<!-- Info Box -->
<div class="info-box">
<p><strong>How it works:</strong></p>
<p><strong>Process:</strong></p>
<ul>
<li>Lead Architect creates initial design</li>
<li>{{ agentCount }} specialists review iteratively</li>
@ -177,7 +177,7 @@ const removeFile = () => {
type="button"
class="submit-btn"
>
{{ isCreating ? 'Starting Session...' : 'Start Design Session' }}
{{ isCreating ? 'Starting...' : 'Start Design Session' }}
</button>
</form>
@ -398,10 +398,11 @@ const removeFile = () => {
background: none;
border: none;
color: #d32f2f;
font-size: 1.2rem;
font-size: 0.85rem;
cursor: pointer;
padding: 0 0.5rem;
padding: 0 0.75rem;
transition: color 0.3s;
font-weight: 600;
}
.remove-btn:hover {

View File

@ -122,7 +122,7 @@ const viewVersion = (versionNumber) => {
<div class="collaborative-session">
<div class="session-header">
<div class="header-content">
<h1>Collaborative Design Session</h1>
<h1>Design Session</h1>
<p class="session-meta">
<span>Session #{{ sessionId }}</span>
<span class="badge" :class="{ active: sessionStarted }">
@ -153,29 +153,29 @@ const viewVersion = (versionNumber) => {
:disabled="!currentDocument"
class="btn btn-outline"
>
Download
Download
</button>
<button
@click="showTimeline = !showTimeline"
class="btn btn-outline"
>
📋 Timeline
Timeline
</button>
</div>
</div>
<!-- Status Message -->
<div v-if="hasConverged" class="convergence-message">
Convergence reached! All agents are satisfied with the document.
Convergence reached. All agents satisfied.
</div>
<!-- Agent List -->
<div class="agents-section">
<h3>Team Members ({{ agents.length }} agents)</h3>
<h3>Team ({{ agents.length }} agents)</h3>
<div class="agents-grid">
<div v-for="agent in agents" :key="agent" class="agent-badge">
👤 {{ formatAgentName(agent) }}
{{ formatAgentName(agent) }}
</div>
</div>
</div>

View File

@ -1,367 +0,0 @@
<template>
<div class="debate-thread">
<div class="debate-header">
<h2>Debate #{{ debate.debateId }}</h2>
<span class="status" :class="currentStatus">{{ currentStatus }}</span>
</div>
<div class="prompt-display">
<strong>Project Prompt:</strong>
<p>{{ debate.prompt }}</p>
</div>
<div class="agents">
<strong>Participating Agents:</strong>
<div class="agent-list">
<span
v-for="agent in debate.agents"
:key="agent"
class="agent-badge"
:class="getAgentClass(agent)"
>
{{ formatAgentName(agent) }}
</span>
</div>
</div>
<!-- Status messages -->
<div v-if="statusMessage" class="status-message">
{{ statusMessage }}
</div>
<!-- Responses -->
<div class="responses" v-if="allResponses.length > 0">
<h3>Debate Responses</h3>
<div
v-for="(response, index) in allResponses"
:key="index"
class="response-card"
:class="getAgentClass(response.agent || response.agent_role)"
>
<div class="response-header">
<span class="agent-name">{{ formatAgentName(response.agent || response.agent_role) }}</span>
<span class="timestamp">{{ formatTimestamp(response.timestamp) }}</span>
</div>
<div class="response-content">
<div v-if="response.response">
<p><strong>Proposal:</strong> {{ response.response.proposal }}</p>
<p v-if="response.response.justification"><strong>Justification:</strong> {{ response.response.justification }}</p>
<p v-if="response.response.confidence"><strong>Confidence:</strong> {{ Math.round(response.response.confidence * 100) }}%</p>
<div v-if="response.response.dependencies && response.response.dependencies.length">
<strong>Dependencies:</strong>
<ul>
<li v-for="dep in response.response.dependencies" :key="dep">{{ dep }}</li>
</ul>
</div>
<!-- Mermaid diagram rendering -->
<div v-if="response.response.mermaid" class="mermaid-container" :ref="`mermaid-${index}`">
<pre class="mermaid">{{ response.response.mermaid.code || response.response.mermaid }}</pre>
</div>
</div>
<div v-else>
{{ typeof response.content === 'string' ? response.content : JSON.stringify(response.content, null, 2) }}
</div>
</div>
</div>
</div>
<div v-else class="no-responses">
<div class="loading-spinner"></div>
<p>AI agents are analyzing your project...</p>
</div>
<!-- Consensus section -->
<div v-if="consensus" class="consensus-section">
<h3>Consensus</h3>
<div class="consensus-card">
<p><strong>Status:</strong> {{ consensus.status }}</p>
<p><strong>Average Confidence:</strong> {{ Math.round(consensus.averageConfidence * 100) }}%</p>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted, nextTick } from 'vue'
import { useWebSocket } from '../composables/useWebSocket'
import mermaid from 'mermaid'
const props = defineProps({
debate: {
type: Object,
required: true
}
})
const allResponses = ref([...(props.debate.responses || [])])
const currentStatus = ref(props.debate.status || 'ongoing')
const statusMessage = ref(null)
const consensus = ref(null)
// Initialize Mermaid
mermaid.initialize({
startOnLoad: true,
theme: 'default',
securityLevel: 'loose'
})
// WebSocket setup
const { messages, connect } = useWebSocket(props.debate.debateId)
onMounted(() => {
connect()
renderMermaidDiagrams()
})
// Watch for WebSocket messages
watch(messages, (newMessages) => {
const latestMessage = newMessages[newMessages.length - 1]
if (!latestMessage) return
switch (latestMessage.type) {
case 'debate_start':
statusMessage.value = latestMessage.message
currentStatus.value = 'ongoing'
break
case 'agent_response':
allResponses.value.push({
agent: latestMessage.agent,
response: latestMessage.response,
timestamp: new Date().toISOString()
})
nextTick(() => renderMermaidDiagrams())
break
case 'debate_complete':
currentStatus.value = 'completed'
statusMessage.value = latestMessage.message
consensus.value = latestMessage.consensus
break
case 'debate_error':
currentStatus.value = 'failed'
statusMessage.value = `Error: ${latestMessage.error}`
break
}
}, { deep: true })
function formatAgentName(agent) {
if (!agent) return ''
return agent
.split('_')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
}
function getAgentClass(agent) {
const classes = {
'architect': 'agent-architect',
'backend_engineer': 'agent-backend',
'frontend_engineer': 'agent-frontend',
'designer': 'agent-designer'
}
return classes[agent] || 'agent-default'
}
function formatTimestamp(timestamp) {
if (!timestamp) return ''
const date = new Date(timestamp)
return date.toLocaleTimeString()
}
async function renderMermaidDiagrams() {
await nextTick()
try {
await mermaid.run({
querySelector: '.mermaid'
})
} catch (error) {
console.error('Mermaid rendering error:', error)
}
}
</script>
<style scoped>
.debate-thread {
max-width: 900px;
margin: 2rem auto;
padding: 2rem;
}
.debate-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.status {
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 600;
}
.status.ongoing {
background-color: #3498db;
color: white;
}
.status.completed {
background-color: #2ecc71;
color: white;
}
.status.failed {
background-color: #e74c3c;
color: white;
}
.prompt-display {
background-color: #f8f9fa;
padding: 1.5rem;
border-radius: 8px;
margin-bottom: 1.5rem;
}
.prompt-display p {
margin: 0.5rem 0 0 0;
line-height: 1.6;
}
.agents {
margin-bottom: 2rem;
}
.agent-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.5rem;
}
.agent-badge {
padding: 0.5rem 1rem;
border-radius: 6px;
font-size: 0.9rem;
font-weight: 500;
color: white;
}
.agent-architect {
background-color: #9b59b6;
}
.agent-backend {
background-color: #3498db;
}
.agent-frontend {
background-color: #1abc9c;
}
.agent-designer {
background-color: #e67e22;
}
.agent-default {
background-color: #95a5a6;
}
.status-message {
background-color: #e8f4f8;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1.5rem;
color: #2c3e50;
text-align: center;
}
.responses h3 {
margin-bottom: 1rem;
}
.response-card {
background-color: white;
border-left: 4px solid;
padding: 1.5rem;
margin-bottom: 1rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.response-header {
display: flex;
justify-content: space-between;
margin-bottom: 1rem;
}
.agent-name {
font-weight: 600;
font-size: 1.1rem;
}
.timestamp {
color: #7f8c8d;
font-size: 0.9rem;
}
.response-content {
line-height: 1.6;
}
.response-content p {
margin-bottom: 0.5rem;
}
.response-content ul {
margin-left: 1.5rem;
}
.mermaid-container {
margin-top: 1rem;
padding: 1rem;
background-color: #f8f9fa;
border-radius: 8px;
overflow-x: auto;
}
.no-responses {
text-align: center;
padding: 3rem;
color: #7f8c8d;
}
.loading-spinner {
margin: 0 auto 1rem;
width: 40px;
height: 40px;
border: 4px solid #e0e0e0;
border-top-color: #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.consensus-section {
margin-top: 2rem;
padding-top: 2rem;
border-top: 2px solid #e0e0e0;
}
.consensus-card {
background-color: #e8f5e9;
padding: 1.5rem;
border-radius: 8px;
border-left: 4px solid #2ecc71;
}
.consensus-card p {
margin: 0.5rem 0;
}
</style>

View File

@ -64,12 +64,12 @@ function getStatusColor() {
function getStatusEmoji() {
switch (quality.value) {
case 'excellent': return ''
case 'good': return ''
case 'fair': return ''
case 'poor': return ''
case 'offline': return ''
default: return '?'
case 'excellent': return ''
case 'good': return ''
case 'fair': return ''
case 'poor': return ''
case 'offline': return ''
default: return ''
}
}
</script>

View File

@ -1,133 +0,0 @@
<template>
<div class="prompt-input">
<h1>Project Agora</h1>
<p class="subtitle">AI-Powered Software Architecture Design</p>
<div class="input-container">
<textarea
v-model="prompt"
placeholder="Describe your project idea... (e.g., 'Build a real-time chat application with user authentication')"
rows="6"
@keydown.ctrl.enter="handleSubmit"
:disabled="loading"
/>
<button
@click="handleSubmit"
:disabled="!prompt.trim() || loading"
class="submit-btn"
>
{{ loading ? 'Creating Debate...' : 'Start AI Debate' }}
</button>
<p v-if="error" class="error">{{ error }}</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useDebateStore } from '../stores/debate'
const emit = defineEmits(['debate-created'])
const debateStore = useDebateStore()
const prompt = ref('')
const loading = ref(false)
const error = ref(null)
async function handleSubmit() {
if (!prompt.value.trim()) return
loading.value = true
error.value = null
try {
const debate = await debateStore.createDebate(prompt.value)
emit('debate-created', debate)
prompt.value = ''
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
</script>
<style scoped>
.prompt-input {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
h1 {
font-size: 3rem;
margin-bottom: 0.5rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.subtitle {
color: #666;
margin-bottom: 2rem;
font-size: 1.1rem;
}
.input-container {
display: flex;
flex-direction: column;
gap: 1rem;
}
textarea {
width: 100%;
padding: 1rem;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-family: inherit;
font-size: 1rem;
resize: vertical;
transition: border-color 0.3s;
}
textarea:focus {
outline: none;
border-color: #667eea;
}
textarea:disabled {
background-color: #f5f5f5;
cursor: not-allowed;
}
.submit-btn {
padding: 1rem 2rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, opacity 0.2s;
}
.submit-btn:hover:not(:disabled) {
transform: translateY(-2px);
}
.submit-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.error {
color: #e74c3c;
font-size: 0.9rem;
margin: 0;
}
</style>

View File

@ -1,97 +0,0 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useDebateStore = defineStore('debate', () => {
const currentDebate = ref(null)
const debates = ref([])
const loading = ref(false)
const error = ref(null)
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000'
/**
* Create a new debate
*/
async function createDebate(prompt) {
loading.value = true
error.value = null
try {
const response = await fetch(`${API_URL}/api/debate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ prompt })
})
if (!response.ok) {
throw new Error('Failed to create debate')
}
const data = await response.json()
currentDebate.value = data
debates.value.unshift(data)
return data
} catch (err) {
error.value = err.message
throw err
} finally {
loading.value = false
}
}
/**
* Get debate by ID
*/
async function getDebate(debateId) {
loading.value = true
error.value = null
try {
const response = await fetch(`${API_URL}/api/debate/${debateId}`)
if (!response.ok) {
throw new Error('Failed to fetch debate')
}
const data = await response.json()
currentDebate.value = data
return data
} catch (err) {
error.value = err.message
throw err
} finally {
loading.value = false
}
}
/**
* Add a response to current debate
*/
function addResponse(response) {
if (currentDebate.value && currentDebate.value.responses) {
currentDebate.value.responses.push(response)
}
}
/**
* Clear current debate
*/
function clearCurrentDebate() {
currentDebate.value = null
}
return {
currentDebate,
debates,
loading,
error,
createDebate,
getDebate,
addResponse,
clearCurrentDebate
}
})