Backend changes: - Refactor mistralClient to generic agent prompts (not role-based) - Implement streaming responses with thinking extraction - Create nameGenerator service for random AI names - Refactor collaborativeOrchestrator for N agents - Implement true convergence (N agents with no changes) - Add section merging for partial document updates - Each AI modifies only ONE section, not entire document - Broadcast agent_working and agent_thinking events in real-time - Update routes for new orchestrator API Features: - Support 3-50 AI agents instead of fixed 7 roles - Real-time thinking/reasoning streaming - Partial document updates (section-based) - True convergence tracking - Automatic round progression - Section extraction and merging Next: Frontend enhancements for visualization
252 lines
7.1 KiB
JavaScript
252 lines
7.1 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"' })
|
|
}
|
|
|
|
// Validate agent count
|
|
const validAgentCount = Math.min(Math.max(agentCount, 3), 50)
|
|
|
|
const sessionId = collaborativeOrchestrator.createSession(
|
|
prompt,
|
|
documentFormat,
|
|
validAgentCount
|
|
)
|
|
|
|
const sessionInfo = collaborativeOrchestrator.getSessionInfo(sessionId)
|
|
|
|
res.json({
|
|
sessionId,
|
|
prompt,
|
|
documentFormat,
|
|
agentCount: validAgentCount,
|
|
status: 'created',
|
|
agents: sessionInfo.agents,
|
|
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
|
|
*/
|
|
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' })
|
|
}
|
|
|
|
res.json({
|
|
sessionId,
|
|
status: 'starting',
|
|
message: 'Session is starting. Initial document is being created...'
|
|
})
|
|
|
|
// Start asynchronously
|
|
collaborativeOrchestrator.startSession(sessionId).catch(error => {
|
|
console.error('Error starting session:', error)
|
|
})
|
|
} catch (error) {
|
|
console.error('Error starting session:', error)
|
|
res.status(500).json({ error: 'Failed to start 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' })
|
|
}
|
|
|
|
const roundNumber = activeSession.conversationHistory.length + 1
|
|
|
|
res.json({
|
|
sessionId,
|
|
roundNumber,
|
|
status: 'running',
|
|
message: 'Review round in progress...'
|
|
})
|
|
|
|
// Run asynchronously
|
|
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
|
|
*/
|
|
router.get('/:id', (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 sessionInfo = collaborativeOrchestrator.getSessionInfo(sessionId)
|
|
const versions = collaborativeOrchestrator.getDocumentVersions(sessionId)
|
|
|
|
res.json({
|
|
sessionId: sessionInfo.id,
|
|
status: sessionInfo.status,
|
|
agents: sessionInfo.agents,
|
|
agentCount: sessionInfo.agentCount,
|
|
currentRound: sessionInfo.currentRound,
|
|
currentDocument: sessionInfo.currentDocument,
|
|
versionNumber: sessionInfo.versionNumber,
|
|
documentVersionCount: versions.length,
|
|
conversationHistory: sessionInfo.conversationHistory,
|
|
createdAt: session.created_at,
|
|
completedAt: session.completed_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 || ''
|
|
|
|
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' })
|
|
}
|
|
|
|
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
|