Compare commits
No commits in common. "0179bf75f2de8b9a9fc8a83a9db2437ecc2a3e3a" and "5a2ca101f8949b6058c288da5fd3eb06c980abf6" have entirely different histories.
0179bf75f2
...
5a2ca101f8
@ -10,7 +10,7 @@ const router = express.Router()
|
|||||||
*/
|
*/
|
||||||
router.post('/', async (req, res) => {
|
router.post('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { prompt, documentFormat = 'md', agentCount = 7, aiProvider = 'mistral' } = req.body
|
const { prompt, documentFormat = 'md', agentCount = 7 } = req.body
|
||||||
|
|
||||||
if (!prompt || prompt.trim().length === 0) {
|
if (!prompt || prompt.trim().length === 0) {
|
||||||
return res.status(400).json({ error: 'Prompt is required' })
|
return res.status(400).json({ error: 'Prompt is required' })
|
||||||
@ -20,18 +20,13 @@ router.post('/', async (req, res) => {
|
|||||||
return res.status(400).json({ error: 'Document format must be "md" or "txt"' })
|
return res.status(400).json({ error: 'Document format must be "md" or "txt"' })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!['mistral', 'claude'].includes(aiProvider)) {
|
|
||||||
return res.status(400).json({ error: 'AI provider must be "mistral" or "claude"' })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate agent count
|
// Validate agent count
|
||||||
const validAgentCount = Math.min(Math.max(agentCount, 3), 50)
|
const validAgentCount = Math.min(Math.max(agentCount, 3), 50)
|
||||||
|
|
||||||
const sessionId = collaborativeOrchestrator.createSession(
|
const sessionId = collaborativeOrchestrator.createSession(
|
||||||
prompt,
|
prompt,
|
||||||
documentFormat,
|
documentFormat,
|
||||||
validAgentCount,
|
validAgentCount
|
||||||
aiProvider
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const sessionInfo = collaborativeOrchestrator.getSessionInfo(sessionId)
|
const sessionInfo = collaborativeOrchestrator.getSessionInfo(sessionId)
|
||||||
@ -41,7 +36,6 @@ router.post('/', async (req, res) => {
|
|||||||
prompt,
|
prompt,
|
||||||
documentFormat,
|
documentFormat,
|
||||||
agentCount: validAgentCount,
|
agentCount: validAgentCount,
|
||||||
aiProvider,
|
|
||||||
status: 'created',
|
status: 'created',
|
||||||
agents: sessionInfo.agents,
|
agents: sessionInfo.agents,
|
||||||
message: 'Collaborative session created. Start the session to begin collaboration.'
|
message: 'Collaborative session created. Start the session to begin collaboration.'
|
||||||
|
|||||||
@ -1,144 +0,0 @@
|
|||||||
import dotenv from 'dotenv'
|
|
||||||
|
|
||||||
dotenv.config()
|
|
||||||
|
|
||||||
const CLAUDE_API_KEY = process.env.CLAUDE_API_KEY
|
|
||||||
const CLAUDE_API_URL = 'https://api.anthropic.com/v1/messages'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic AI prompt for collaborative document editing
|
|
||||||
*/
|
|
||||||
function getAgentPrompt(agentName) {
|
|
||||||
return `You are an AI assistant named ${agentName} collaborating on a technical document design.
|
|
||||||
|
|
||||||
Your responsibilities:
|
|
||||||
1. Review the current document structure
|
|
||||||
2. Either:
|
|
||||||
a) Modify ONE existing section (identified by #, ##, ###, #### headers), OR
|
|
||||||
b) Create a NEW section if you think it's needed, OR
|
|
||||||
c) Delete a section if you think it's redundant or not useful
|
|
||||||
3. Provide your thinking process and reasoning
|
|
||||||
4. Return ONLY the section (modified or new) with its header, or command to delete, or confirm it's good as-is
|
|
||||||
|
|
||||||
CRITICAL RULES - FOLLOW THESE EXACTLY:
|
|
||||||
- Work on exactly ONE section only
|
|
||||||
- NEVER return the entire document
|
|
||||||
- NEVER return multiple sections
|
|
||||||
- Return ONLY the section you're working on, not the whole document
|
|
||||||
- You CAN create a new section if document is missing important content
|
|
||||||
- You CAN delete a section if it's redundant, duplicate, or not useful
|
|
||||||
- To delete a section, respond: "DELETE: ## Section Name" (with exact header)
|
|
||||||
- If section is good, respond: "Section is good, no changes needed"
|
|
||||||
- Think step-by-step about what could be improved or removed
|
|
||||||
- Share your reasoning process
|
|
||||||
|
|
||||||
RESPONSE FORMAT - FOLLOW THIS EXACTLY:
|
|
||||||
THINKING: [Your analysis and reasoning about the current document]
|
|
||||||
DECISION: [Exactly what you will do: modify section X, create new section Y, delete section Z, or keep as-is]
|
|
||||||
SECTION:
|
|
||||||
[ONLY ONE: Either a markdown section starting with # or ##, a DELETE command, or the text "Section is good, no changes needed"]
|
|
||||||
|
|
||||||
EXAMPLE OF CORRECT RESPONSE:
|
|
||||||
THINKING: The Overview section is too brief and doesn't explain the main purpose.
|
|
||||||
DECISION: I will modify the Overview section to be more comprehensive.
|
|
||||||
SECTION:
|
|
||||||
## Overview
|
|
||||||
This is a technical document for designing system architecture...
|
|
||||||
|
|
||||||
EXAMPLE OF INCORRECT RESPONSE (DO NOT DO THIS):
|
|
||||||
[The entire document repeated here] <- WRONG!`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call Claude API
|
|
||||||
*/
|
|
||||||
async function callClaudeAPI(messages) {
|
|
||||||
const response = await fetch(CLAUDE_API_URL, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'x-api-key': CLAUDE_API_KEY,
|
|
||||||
'anthropic-version': '2023-06-01'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
model: 'claude-3-5-sonnet-20241022',
|
|
||||||
max_tokens: 2048,
|
|
||||||
system: messages[0].content,
|
|
||||||
messages: messages.slice(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const error = await response.text()
|
|
||||||
throw new Error(`Claude API error: ${error}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate agent response using Claude
|
|
||||||
*/
|
|
||||||
export async function generateAgentResponseSync(agentName, prompt, currentDocument = '') {
|
|
||||||
const systemPrompt = getAgentPrompt(agentName)
|
|
||||||
|
|
||||||
const messages = [
|
|
||||||
{ role: 'user', content: systemPrompt },
|
|
||||||
{ role: 'user', content: `Project description: ${prompt}\n\nCurrent document:\n${currentDocument}` }
|
|
||||||
]
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await callClaudeAPI(messages)
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
if (!data.content?.[0]?.text) {
|
|
||||||
throw new Error('Invalid response from Claude API')
|
|
||||||
}
|
|
||||||
|
|
||||||
return data.content[0].text
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error generating response from ${agentName}:`, error)
|
|
||||||
return `Error: ${error.message}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract section from AI response
|
|
||||||
* Validates that we get a proper section, not the entire document
|
|
||||||
*/
|
|
||||||
export function extractSection(aiResponse) {
|
|
||||||
const sectionMatch = aiResponse.match(/SECTION:\s*([\s\S]*?)(?:$)/)
|
|
||||||
if (sectionMatch) {
|
|
||||||
const extracted = sectionMatch[1].trim()
|
|
||||||
|
|
||||||
// Validate: extracted should be either:
|
|
||||||
// 1. A markdown section starting with # (DELETE: or "Section is good...")
|
|
||||||
// 2. OR a single section with < 5000 chars (not entire document)
|
|
||||||
const isMarkdownSection = /^(#|DELETE:|Section is good)/.test(extracted)
|
|
||||||
const isShortEnough = extracted.length < 5000 // Single section should be < 5KB
|
|
||||||
|
|
||||||
if (isMarkdownSection || isShortEnough) {
|
|
||||||
return extracted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: if no SECTION: tag found, treat entire response as section
|
|
||||||
// but only if it starts with markdown header or is a command
|
|
||||||
if (/^(#|DELETE:|Section is good)/.test(aiResponse)) {
|
|
||||||
return aiResponse.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
// If none of the above, return error indicator
|
|
||||||
return "ERROR: Response does not contain a valid section format"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract thinking from AI response
|
|
||||||
*/
|
|
||||||
export function extractThinking(aiResponse) {
|
|
||||||
const thinkingMatch = aiResponse.match(/THINKING:\s*([\s\S]*?)(?:DECISION:|SECTION:)/)
|
|
||||||
if (thinkingMatch) {
|
|
||||||
return thinkingMatch[1].trim()
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import db from '../db/schema.js'
|
import db from '../db/schema.js'
|
||||||
import * as mistralClient from './mistralClient.js'
|
import { generateAgentResponseSync, extractSection, extractThinking } from './mistralClient.js'
|
||||||
import * as claudeClient from './claudeClient.js'
|
|
||||||
import { getRandomNames } from './nameGenerator.js'
|
import { getRandomNames } from './nameGenerator.js'
|
||||||
|
|
||||||
class CollaborativeOrchestrator {
|
class CollaborativeOrchestrator {
|
||||||
@ -45,13 +44,13 @@ class CollaborativeOrchestrator {
|
|||||||
/**
|
/**
|
||||||
* Create a new collaborative session with N random-named agents
|
* Create a new collaborative session with N random-named agents
|
||||||
*/
|
*/
|
||||||
createSession(initialPrompt, documentFormat = 'md', agentCount = 7, aiProvider = 'mistral') {
|
createSession(initialPrompt, documentFormat = 'md', agentCount = 7) {
|
||||||
const stmt = db.prepare(
|
const stmt = db.prepare(
|
||||||
'INSERT INTO collaborative_sessions (initial_prompt, document_format, status) VALUES (?, ?, ?)'
|
'INSERT INTO collaborative_sessions (initial_prompt, document_format, status) VALUES (?, ?, ?)'
|
||||||
)
|
)
|
||||||
const result = stmt.run(initialPrompt, documentFormat, 'created')
|
const result = stmt.run(initialPrompt, documentFormat, 'created')
|
||||||
const sessionId = result.lastInsertRowid
|
const sessionId = result.lastInsertRowid
|
||||||
console.log(`[Session ${sessionId}] Created with ${agentCount} agents, format: ${documentFormat}, provider: ${aiProvider}`)
|
console.log(`[Session ${sessionId}] Created with ${agentCount} agents, format: ${documentFormat}`)
|
||||||
|
|
||||||
// Generate random names for agents
|
// Generate random names for agents
|
||||||
const agentNames = getRandomNames(Math.min(agentCount, 50))
|
const agentNames = getRandomNames(Math.min(agentCount, 50))
|
||||||
@ -60,7 +59,6 @@ class CollaborativeOrchestrator {
|
|||||||
id: sessionId,
|
id: sessionId,
|
||||||
initialPrompt,
|
initialPrompt,
|
||||||
documentFormat,
|
documentFormat,
|
||||||
aiProvider, // Claude or Mistral
|
|
||||||
agents: agentNames, // Array of agent names
|
agents: agentNames, // Array of agent names
|
||||||
agentCount,
|
agentCount,
|
||||||
currentAgentIndex: 0,
|
currentAgentIndex: 0,
|
||||||
@ -181,15 +179,14 @@ class CollaborativeOrchestrator {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`[Session ${sessionId}] ${agentName} analyzing and generating response...`)
|
console.log(`[Session ${sessionId}] ${agentName} analyzing and generating response...`)
|
||||||
const client = session.aiProvider === 'claude' ? claudeClient : mistralClient
|
const response = await generateAgentResponseSync(
|
||||||
const response = await client.generateAgentResponseSync(
|
|
||||||
agentName,
|
agentName,
|
||||||
session.initialPrompt,
|
session.initialPrompt,
|
||||||
session.currentDocument // <-- This is always the latest version
|
session.currentDocument // <-- This is always the latest version
|
||||||
)
|
)
|
||||||
|
|
||||||
const thinking = client.extractThinking(response)
|
const thinking = extractThinking(response)
|
||||||
const section = client.extractSection(response)
|
const section = extractSection(response)
|
||||||
|
|
||||||
console.log(`[Session ${sessionId}] ${agentName} response received (${response.length} chars)`)
|
console.log(`[Session ${sessionId}] ${agentName} response received (${response.length} chars)`)
|
||||||
console.log(`[Session ${sessionId}] --- THINKING (${agentName}) ---`)
|
console.log(`[Session ${sessionId}] --- THINKING (${agentName}) ---`)
|
||||||
|
|||||||
@ -9,23 +9,24 @@ const collaborationStore = useCollaborationStore()
|
|||||||
const prompt = ref('')
|
const prompt = ref('')
|
||||||
const contextFile = ref(null)
|
const contextFile = ref(null)
|
||||||
const agentCount = ref(7)
|
const agentCount = ref(7)
|
||||||
const aiProvider = ref('mistral')
|
|
||||||
const documentFormat = ref('md')
|
|
||||||
const isCreating = ref(false)
|
const isCreating = ref(false)
|
||||||
|
const previousSessions = ref([])
|
||||||
|
const loadingPreviousSessions = ref(false)
|
||||||
const showAllSessions = ref(false)
|
const showAllSessions = ref(false)
|
||||||
|
const showArchives = ref(false)
|
||||||
const sessionStatusFilter = ref('all') // 'all', 'completed', 'ongoing', 'created'
|
const sessionStatusFilter = ref('all') // 'all', 'completed', 'ongoing', 'created'
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
const isFetchingSessions = ref(false)
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (collaborationStore.sessionsLoaded.value) return
|
loadingPreviousSessions.value = true
|
||||||
isFetchingSessions.value = true
|
|
||||||
try {
|
try {
|
||||||
await collaborationStore.fetchSessions()
|
const response = await fetch('/api/collaborate')
|
||||||
|
const data = await response.json()
|
||||||
|
previousSessions.value = data.sessions || []
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading previous sessions:', error)
|
console.error('Error loading previous sessions:', error)
|
||||||
} finally {
|
} finally {
|
||||||
isFetchingSessions.value = false
|
loadingPreviousSessions.value = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -33,8 +34,6 @@ const handleOpenSession = (sessionId) => {
|
|||||||
emit('session-created', { sessionId })
|
emit('session-created', { sessionId })
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessions = computed(() => collaborationStore.sessions.value)
|
|
||||||
|
|
||||||
const agentOptions = computed(() => {
|
const agentOptions = computed(() => {
|
||||||
return Array.from({ length: 48 }, (_, i) => ({
|
return Array.from({ length: 48 }, (_, i) => ({
|
||||||
value: i + 3,
|
value: i + 3,
|
||||||
@ -43,7 +42,7 @@ const agentOptions = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const filteredSessions = computed(() => {
|
const filteredSessions = computed(() => {
|
||||||
let filtered = sessions.value
|
let filtered = previousSessions.value
|
||||||
|
|
||||||
if (sessionStatusFilter.value !== 'all') {
|
if (sessionStatusFilter.value !== 'all') {
|
||||||
filtered = filtered.filter(s => s.status === sessionStatusFilter.value)
|
filtered = filtered.filter(s => s.status === sessionStatusFilter.value)
|
||||||
@ -65,11 +64,11 @@ const displayedSessions = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const completedCount = computed(() => {
|
const completedCount = computed(() => {
|
||||||
return sessions.value.filter(s => s.status === 'completed').length
|
return previousSessions.value.filter(s => s.status === 'completed').length
|
||||||
})
|
})
|
||||||
|
|
||||||
const ongoingCount = computed(() => {
|
const ongoingCount = computed(() => {
|
||||||
return sessions.value.filter(s => s.status === 'ongoing').length
|
return previousSessions.value.filter(s => s.status === 'ongoing').length
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleFileSelect = (event) => {
|
const handleFileSelect = (event) => {
|
||||||
@ -108,9 +107,8 @@ const handleCreateSession = async () => {
|
|||||||
// Always use 'md' format for output
|
// Always use 'md' format for output
|
||||||
const session = await collaborationStore.createSession(
|
const session = await collaborationStore.createSession(
|
||||||
finalPrompt,
|
finalPrompt,
|
||||||
documentFormat.value,
|
'md',
|
||||||
agentCount.value,
|
agentCount.value
|
||||||
aiProvider.value
|
|
||||||
)
|
)
|
||||||
|
|
||||||
emit('session-created', session)
|
emit('session-created', session)
|
||||||
@ -119,8 +117,6 @@ const handleCreateSession = async () => {
|
|||||||
prompt.value = ''
|
prompt.value = ''
|
||||||
contextFile.value = null
|
contextFile.value = null
|
||||||
agentCount.value = 7
|
agentCount.value = 7
|
||||||
aiProvider.value = 'mistral'
|
|
||||||
documentFormat.value = 'md'
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert(`Error creating session: ${collaborationStore.error}`)
|
alert(`Error creating session: ${collaborationStore.error}`)
|
||||||
} finally {
|
} finally {
|
||||||
@ -151,11 +147,7 @@ const removeFile = () => {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Quick Access Section -->
|
<!-- Quick Access Section -->
|
||||||
<div v-if="isFetchingSessions" class="loading-sessions">
|
<div v-if="previousSessions.length > 0" class="quick-access-section">
|
||||||
Loading previous sessions...
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="sessions.length > 0" class="quick-access-section">
|
|
||||||
<div class="stats-bar">
|
<div class="stats-bar">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<span class="stat-label">Completed</span>
|
<span class="stat-label">Completed</span>
|
||||||
@ -167,7 +159,7 @@ const removeFile = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<span class="stat-label">Total</span>
|
<span class="stat-label">Total</span>
|
||||||
<span class="stat-value">{{ sessions.length }}</span>
|
<span class="stat-value">{{ previousSessions.length }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -188,7 +180,7 @@ const removeFile = () => {
|
|||||||
class="filter-btn"
|
class="filter-btn"
|
||||||
:class="{ active: sessionStatusFilter === 'all' }"
|
:class="{ active: sessionStatusFilter === 'all' }"
|
||||||
>
|
>
|
||||||
All Sessions ({{ sessions.length }})
|
All Sessions ({{ previousSessions.length }})
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="sessionStatusFilter = 'completed'"
|
@click="sessionStatusFilter = 'completed'"
|
||||||
@ -242,7 +234,7 @@ const removeFile = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Divider -->
|
<!-- Divider -->
|
||||||
<div v-if="sessions.length > 0" class="divider">
|
<div v-if="previousSessions.length > 0" class="divider">
|
||||||
<span>Or start a new session</span>
|
<span>Or start a new session</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -298,7 +290,7 @@ const removeFile = () => {
|
|||||||
<p class="hint">Optional: Provide existing documentation or requirements to guide the design.</p>
|
<p class="hint">Optional: Provide existing documentation or requirements to guide the design.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Agent Count and AI Provider Selection -->
|
<!-- Agent Count Selection -->
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="agents" class="label">Number of AI Specialists</label>
|
<label for="agents" class="label">Number of AI Specialists</label>
|
||||||
@ -309,22 +301,6 @@ const removeFile = () => {
|
|||||||
</select>
|
</select>
|
||||||
<p class="hint">More agents = more diverse perspectives.</p>
|
<p class="hint">More agents = more diverse perspectives.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<label for="provider" class="label">AI Provider</label>
|
|
||||||
<select v-model="aiProvider" id="provider" class="select">
|
|
||||||
<option value="mistral">Mistral (Large 2411)</option>
|
|
||||||
<option value="claude">Claude (3.5 Sonnet)</option>
|
|
||||||
</select>
|
|
||||||
<p class="hint">Choose the AI model for specialists.</p>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="format" class="label">Document Format</label>
|
|
||||||
<select v-model="documentFormat" id="format" class="select">
|
|
||||||
<option value="md">Markdown (.md)</option>
|
|
||||||
<option value="txt">Plain Text (.txt)</option>
|
|
||||||
</select>
|
|
||||||
<p class="hint">Markdown recommended for structured documents.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Info Box -->
|
<!-- Info Box -->
|
||||||
@ -637,17 +613,6 @@ const removeFile = () => {
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-sessions {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
padding: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
color: rgba(255, 255, 255, 0.7);
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
||||||
border-radius: 12px;
|
|
||||||
backdrop-filter: blur(8px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-input-wrapper {
|
.file-input-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
|||||||
@ -80,19 +80,11 @@ const handleWebSocketMessage = (message) => {
|
|||||||
if (message.type === 'initial_document_created') {
|
if (message.type === 'initial_document_created') {
|
||||||
sessionStarted.value = true
|
sessionStarted.value = true
|
||||||
collaborationStore.currentDocument = message.content
|
collaborationStore.currentDocument = message.content
|
||||||
collaborationStore.currentRound = 0
|
|
||||||
if (collaborationStore.currentSession) {
|
|
||||||
collaborationStore.currentSession.status = 'ongoing'
|
|
||||||
collaborationStore.currentSession.currentDocument = message.content
|
|
||||||
}
|
|
||||||
currentWorkingAgent.value = null
|
currentWorkingAgent.value = null
|
||||||
currentAgentThinking.value = ''
|
currentAgentThinking.value = ''
|
||||||
scheduleNextRound(2000)
|
scheduleNextRound(2000)
|
||||||
} else if (message.type === 'document_modified') {
|
} else if (message.type === 'document_modified') {
|
||||||
collaborationStore.currentDocument = message.content
|
collaborationStore.currentDocument = message.content
|
||||||
if (collaborationStore.currentSession) {
|
|
||||||
collaborationStore.currentSession.currentDocument = message.content
|
|
||||||
}
|
|
||||||
} else if (message.type === 'agent_working') {
|
} else if (message.type === 'agent_working') {
|
||||||
currentWorkingAgent.value = message.agentName
|
currentWorkingAgent.value = message.agentName
|
||||||
currentAgentThinking.value = ''
|
currentAgentThinking.value = ''
|
||||||
@ -100,17 +92,11 @@ const handleWebSocketMessage = (message) => {
|
|||||||
currentAgentThinking.value = message.thinking || ''
|
currentAgentThinking.value = message.thinking || ''
|
||||||
} else if (message.type === 'round_complete') {
|
} else if (message.type === 'round_complete') {
|
||||||
isRunningRound.value = false
|
isRunningRound.value = false
|
||||||
const roundEntry = {
|
collaborationStore.conversationHistory.push({
|
||||||
roundNumber: message.roundNumber,
|
roundNumber: message.roundNumber,
|
||||||
agentsMadeChanges: message.agentsMadeChanges,
|
agentsMadeChanges: message.agentsMadeChanges,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
}
|
})
|
||||||
collaborationStore.conversationHistory.push(roundEntry)
|
|
||||||
if (collaborationStore.currentSession) {
|
|
||||||
const history = collaborationStore.currentSession.conversationHistory || []
|
|
||||||
collaborationStore.currentSession.conversationHistory = [...history, roundEntry]
|
|
||||||
}
|
|
||||||
collaborationStore.currentRound = message.roundNumber
|
|
||||||
currentWorkingAgent.value = null
|
currentWorkingAgent.value = null
|
||||||
currentAgentThinking.value = ''
|
currentAgentThinking.value = ''
|
||||||
|
|
||||||
@ -130,9 +116,6 @@ const handleWebSocketMessage = (message) => {
|
|||||||
} else if (message.type === 'session_completed') {
|
} else if (message.type === 'session_completed') {
|
||||||
currentWorkingAgent.value = null
|
currentWorkingAgent.value = null
|
||||||
isAutoRunning.value = false
|
isAutoRunning.value = false
|
||||||
if (collaborationStore.currentSession) {
|
|
||||||
collaborationStore.currentSession.status = 'completed'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,10 +237,6 @@ function formatAgentName(agent) {
|
|||||||
<span class="hand">✋</span>
|
<span class="hand">✋</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="agent-name-working">{{ formatAgentName(currentWorkingAgent) }}</div>
|
<div class="agent-name-working">{{ formatAgentName(currentWorkingAgent) }}</div>
|
||||||
<div class="agent-thinking">
|
|
||||||
<span v-if="currentAgentThinking">{{ currentAgentThinking }}</span>
|
|
||||||
<span v-else>Analyzing the document...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Convergence Message -->
|
<!-- Convergence Message -->
|
||||||
@ -482,16 +461,6 @@ function formatAgentName(agent) {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-thinking {
|
|
||||||
flex: 1;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
color: rgba(255, 255, 255, 0.75);
|
|
||||||
background: rgba(102, 126, 234, 0.08);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.convergence-message {
|
.convergence-message {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, watch, ref, nextTick } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { marked } from 'marked'
|
import { marked } from 'marked'
|
||||||
import hljs from 'highlight.js'
|
import hljs from 'highlight.js'
|
||||||
import mermaid from 'mermaid'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
document: {
|
document: {
|
||||||
@ -24,28 +23,22 @@ marked.setOptions({
|
|||||||
|
|
||||||
const renderer = new marked.Renderer()
|
const renderer = new marked.Renderer()
|
||||||
|
|
||||||
// Configure mermaid once
|
// Override code block rendering to add syntax highlighting
|
||||||
mermaid.initialize({
|
|
||||||
startOnLoad: false,
|
|
||||||
theme: 'dark',
|
|
||||||
securityLevel: 'loose'
|
|
||||||
})
|
|
||||||
|
|
||||||
// Override code block rendering to handle Mermaid and syntax highlighting
|
|
||||||
renderer.code = ({ text, lang }) => {
|
renderer.code = ({ text, lang }) => {
|
||||||
if ((lang || '').toLowerCase() === 'mermaid') {
|
const language = lang || 'plain'
|
||||||
return `<div class="mermaid">${text}</div>`
|
let highlighted = text
|
||||||
|
|
||||||
|
if (hljs.getLanguage(language)) {
|
||||||
|
highlighted = hljs.highlight(text, { language }).value
|
||||||
|
} else {
|
||||||
|
highlighted = hljs.highlight(text, { language: 'plain' }).value
|
||||||
}
|
}
|
||||||
|
|
||||||
const language = lang && hljs.getLanguage(lang) ? lang : 'plaintext'
|
|
||||||
const highlighted = hljs.highlight(text, { language }).value
|
|
||||||
return `<pre><code class="hljs language-${language}">${highlighted}</code></pre>`
|
return `<pre><code class="hljs language-${language}">${highlighted}</code></pre>`
|
||||||
}
|
}
|
||||||
|
|
||||||
marked.setOptions({ renderer })
|
marked.setOptions({ renderer })
|
||||||
|
|
||||||
const containerRef = ref(null)
|
|
||||||
|
|
||||||
const renderedContent = computed(() => {
|
const renderedContent = computed(() => {
|
||||||
if (!props.document) {
|
if (!props.document) {
|
||||||
return '<p class="empty-state">No document content yet. Start the session to begin collaboration.</p>'
|
return '<p class="empty-state">No document content yet. Start the session to begin collaboration.</p>'
|
||||||
@ -64,40 +57,18 @@ const renderedContent = computed(() => {
|
|||||||
return `<pre><code>${escaped}</code></pre>`
|
return `<pre><code>${escaped}</code></pre>`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
async function renderMermaid() {
|
|
||||||
await nextTick()
|
|
||||||
if (!containerRef.value) return
|
|
||||||
|
|
||||||
const nodes = containerRef.value.querySelectorAll('.mermaid')
|
|
||||||
if (!nodes.length) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
await mermaid.run({ nodes })
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Mermaid render error:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
renderMermaid()
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => [props.document, props.format], () => {
|
|
||||||
renderMermaid()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="document-viewer">
|
<div class="document-viewer">
|
||||||
<div class="document-container" :class="`format-${format}`">
|
<div class="document-container" :class="`format-${format}`">
|
||||||
<div ref="containerRef" v-html="renderedContent" class="document-content"></div>
|
<div v-html="renderedContent" class="document-content"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@import 'highlight.js/styles/atom-one-dark.css';
|
:import 'highlight.js/styles/atom-one-dark.css';
|
||||||
|
|
||||||
.document-viewer {
|
.document-viewer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@ -9,40 +9,13 @@ export const useCollaborationStore = defineStore('collaboration', () => {
|
|||||||
const currentDocument = ref('')
|
const currentDocument = ref('')
|
||||||
const currentRound = ref(0)
|
const currentRound = ref(0)
|
||||||
const conversationHistory = ref([])
|
const conversationHistory = ref([])
|
||||||
const sessionsLoaded = ref(false)
|
|
||||||
|
|
||||||
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000'
|
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000'
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch recent collaborative sessions
|
|
||||||
*/
|
|
||||||
async function fetchSessions() {
|
|
||||||
if (loading.value) return
|
|
||||||
loading.value = true
|
|
||||||
error.value = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${API_URL}/api/collaborate`)
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to fetch sessions')
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
sessions.value = data.sessions || []
|
|
||||||
sessionsLoaded.value = true
|
|
||||||
return sessions.value
|
|
||||||
} catch (err) {
|
|
||||||
error.value = err.message
|
|
||||||
throw err
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new collaborative session
|
* Create a new collaborative session
|
||||||
*/
|
*/
|
||||||
async function createSession(prompt, documentFormat = 'md', agentCount = 7, aiProvider = 'mistral') {
|
async function createSession(prompt, documentFormat = 'md', agentCount = 7) {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
|
|
||||||
@ -55,8 +28,7 @@ export const useCollaborationStore = defineStore('collaboration', () => {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
prompt,
|
prompt,
|
||||||
documentFormat,
|
documentFormat,
|
||||||
agentCount,
|
agentCount
|
||||||
aiProvider
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -66,8 +38,7 @@ export const useCollaborationStore = defineStore('collaboration', () => {
|
|||||||
|
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
currentSession.value = data
|
currentSession.value = data
|
||||||
sessions.value = [data, ...sessions.value.filter(s => s.sessionId !== data.sessionId)]
|
sessions.value.unshift(data)
|
||||||
sessionsLoaded.value = true
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -181,24 +152,7 @@ export const useCollaborationStore = defineStore('collaboration', () => {
|
|||||||
throw new Error('Failed to complete session')
|
throw new Error('Failed to complete session')
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json()
|
return await response.json()
|
||||||
|
|
||||||
if (currentSession.value && currentSession.value.sessionId === sessionId) {
|
|
||||||
currentSession.value = {
|
|
||||||
...currentSession.value,
|
|
||||||
status: 'completed',
|
|
||||||
completedAt: new Date().toISOString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sessions.value = sessions.value.map(session => {
|
|
||||||
if (session.sessionId === sessionId) {
|
|
||||||
return { ...session, status: 'completed', completedAt: new Date().toISOString() }
|
|
||||||
}
|
|
||||||
return session
|
|
||||||
})
|
|
||||||
|
|
||||||
return data
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error.value = err.message
|
error.value = err.message
|
||||||
throw err
|
throw err
|
||||||
@ -236,13 +190,11 @@ export const useCollaborationStore = defineStore('collaboration', () => {
|
|||||||
return {
|
return {
|
||||||
currentSession,
|
currentSession,
|
||||||
sessions,
|
sessions,
|
||||||
sessionsLoaded,
|
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
currentDocument,
|
currentDocument,
|
||||||
currentRound,
|
currentRound,
|
||||||
conversationHistory,
|
conversationHistory,
|
||||||
fetchSessions,
|
|
||||||
createSession,
|
createSession,
|
||||||
startSession,
|
startSession,
|
||||||
runRound,
|
runRound,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user