agora-ai/backend/src/routes/collaborate.js
Muyue 85319b5ca6 Fix session lifecycle: change initial status from 'ongoing' to 'created'
Update database schema and backend services to properly track session lifecycle:
- Sessions now start with 'created' status
- Frontend auto-start logic works when status is 'created'
- Status transitions to 'ongoing' when session actually starts
- Prevents issues with premature round execution

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 17:38:41 +02:00

265 lines
7.6 KiB
JavaScript

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 !== 'created') {
return res.status(400).json({ error: 'Session has already been started or is no longer available' });
}
// 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;