Compare commits
No commits in common. "31cd84186f316ebf538ae39c519b37420a302c81" and "610d4bb686e6153c68ef75e1eda1b65176f74abb" have entirely different histories.
31cd84186f
...
610d4bb686
33
README.md
33
README.md
@ -21,15 +21,15 @@ Aucune génération de code direct n'est effectuée : seules des **explications,
|
||||
|
||||
Reproduire une **table ronde d'IA spécialistes**, chaque agent IA représentant un rôle spécifique :
|
||||
|
||||
- Lead Architect (Architecte logiciel)
|
||||
- Backend Engineer (Développeur backend)
|
||||
- Frontend Engineer (Développeur frontend)
|
||||
- UI Designer (Designer UI/UX)
|
||||
- DevOps Engineer (Ingénieur DevOps)
|
||||
- Product Manager (Chef de projet)
|
||||
- Security Specialist (Spécialiste sécurité)
|
||||
- Architecte logiciel
|
||||
- Développeur backend
|
||||
- Développeur frontend
|
||||
- Designer UI/UX
|
||||
- Data engineer
|
||||
- Chef de projet
|
||||
- Éventuellement d'autres rôles selon la complexité du prompt.
|
||||
|
||||
L'aspect le plus crucial de ces IA réside dans leur capacité à **collaborer itérativement** pour améliorer collectivement la spécification architecturale jusqu'à convergence naturelle (quand plus aucune amélioration n'est proposée).
|
||||
Cependant, l'aspect le plus crucial de ces IA réside dans leur capacité à **échanger, négocier, voire s'opposer** pour déterminer collectivement la meilleure approche à adopter.
|
||||
|
||||
---
|
||||
|
||||
@ -96,17 +96,16 @@ graph TD
|
||||
|
||||
### Interface et visualisation
|
||||
- **Saisie de prompt** décrivant le projet souhaité
|
||||
- **Upload optionnel** de fichiers contexte (MD/TXT)
|
||||
- **Visualisation en temps réel** des modifications via WebSocket
|
||||
- **Timeline d'évolution** du document avec historique complet
|
||||
- **Affichage interactif** du document Markdown final
|
||||
- **Indicateurs de progression** et statuts du session
|
||||
- **Visualisation en temps réel** des échanges entre agents via WebSocket
|
||||
- **Affichage structuré** des propositions avec justifications et niveaux de confiance
|
||||
- **Rendu automatique** de diagrammes Mermaid intégrés dans les réponses
|
||||
- **Indicateurs de progression** et statuts du débat
|
||||
|
||||
### Architecture et stockage
|
||||
- **API REST** pour gestion des sessions collaboratives
|
||||
- **WebSocket** pour streaming temps réel des modifications d'agents
|
||||
- **Base SQLite** pour persistance des sessions, versions et historique complet
|
||||
- **Versioning intelligent** : Chaque modification crée une version avec métadonnées (agent, raison, round)
|
||||
- **API REST** pour gestion des débats et récupération des résultats
|
||||
- **WebSocket** pour streaming temps réel des réponses des agents
|
||||
- **Base SQLite** pour persistance des débats et historique
|
||||
- **Gestion d'erreurs** avec fallbacks et réponses de secours
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ db.exec(`
|
||||
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 ('created', 'ongoing', 'completed', 'failed')) DEFAULT 'created',
|
||||
status TEXT CHECK(status IN ('ongoing', 'completed', 'failed')) DEFAULT 'ongoing',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
completed_at TIMESTAMP,
|
||||
final_document TEXT
|
||||
|
||||
@ -55,8 +55,8 @@ router.post('/:id/start', async (req, res) => {
|
||||
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' });
|
||||
if (session.status !== 'ongoing') {
|
||||
return res.status(400).json({ error: 'Session is not in ongoing status' });
|
||||
}
|
||||
|
||||
// Start the session asynchronously
|
||||
|
||||
@ -58,7 +58,7 @@ class CollaborativeOrchestrator {
|
||||
const stmt = db.prepare(
|
||||
'INSERT INTO collaborative_sessions (initial_prompt, document_format, status) VALUES (?, ?, ?)'
|
||||
);
|
||||
const result = stmt.run(initialPrompt, documentFormat, 'created');
|
||||
const result = stmt.run(initialPrompt, documentFormat, 'ongoing');
|
||||
const sessionId = result.lastInsertRowid;
|
||||
|
||||
// Select the agents to use
|
||||
@ -145,10 +145,6 @@ class CollaborativeOrchestrator {
|
||||
throw new Error('Active session not found');
|
||||
}
|
||||
|
||||
// Update session status from 'created' to 'ongoing'
|
||||
const updateStmt = db.prepare('UPDATE collaborative_sessions SET status = ? WHERE id = ?');
|
||||
updateStmt.run('ongoing', sessionId);
|
||||
|
||||
activeSession.started = true;
|
||||
|
||||
// Broadcast session start
|
||||
|
||||
@ -3,7 +3,6 @@ import { ref } from 'vue'
|
||||
import { useCollaborationStore } from './stores/collaboration'
|
||||
import CollaborativeInput from './components/CollaborativeInput.vue'
|
||||
import CollaborativeSession from './components/CollaborativeSession.vue'
|
||||
import TimelinePanel from './components/TimelinePanel.vue'
|
||||
import NetworkStatus from './components/NetworkStatus.vue'
|
||||
|
||||
const collaborationStore = useCollaborationStore()
|
||||
@ -26,234 +25,72 @@ function startNewCollaboration() {
|
||||
<div class="app">
|
||||
<NetworkStatus />
|
||||
|
||||
<!-- Input Mode - Centered Full Screen -->
|
||||
<div v-if="!showSession" class="input-container">
|
||||
<!-- Input Mode -->
|
||||
<div v-if="!showSession">
|
||||
<CollaborativeInput @session-created="handleCollaborationCreated" />
|
||||
</div>
|
||||
|
||||
<!-- Session Mode - Two Column Layout -->
|
||||
<div v-else class="session-layout">
|
||||
<!-- Left Sidebar: Timeline -->
|
||||
<div class="timeline-sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h2>Session Progress</h2>
|
||||
<button @click="startNewCollaboration" class="new-btn-small" title="Start new session">
|
||||
R
|
||||
</button>
|
||||
</div>
|
||||
<!-- Session Mode -->
|
||||
<div v-else>
|
||||
<button @click="startNewCollaboration" class="new-session-btn">
|
||||
New Session
|
||||
</button>
|
||||
|
||||
<!-- Timeline Panel -->
|
||||
<TimelinePanel />
|
||||
</div>
|
||||
|
||||
<!-- Right Content: Session -->
|
||||
<div class="main-content">
|
||||
<CollaborativeSession
|
||||
v-if="currentSessionId"
|
||||
:session-id="currentSessionId"
|
||||
@session-completed="startNewCollaboration"
|
||||
/>
|
||||
</div>
|
||||
<CollaborativeSession
|
||||
v-if="currentSessionId"
|
||||
:session-id="currentSessionId"
|
||||
@session-completed="startNewCollaboration"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@keyframes gradient-shift {
|
||||
0% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@keyframes float1 {
|
||||
0%, 100% { transform: translate(0, 0) rotate(0deg); opacity: 0.03; }
|
||||
25% { transform: translate(100px, -100px) rotate(90deg); opacity: 0.05; }
|
||||
50% { transform: translate(-50px, 150px) rotate(180deg); opacity: 0.03; }
|
||||
75% { transform: translate(-100px, -50px) rotate(270deg); opacity: 0.04; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background-color: #f5f7fa;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
@keyframes float2 {
|
||||
0%, 100% { transform: translate(0, 0) rotate(0deg); opacity: 0.04; }
|
||||
25% { transform: translate(-120px, 80px) rotate(-90deg); opacity: 0.06; }
|
||||
50% { transform: translate(80px, -120px) rotate(-180deg); opacity: 0.04; }
|
||||
75% { transform: translate(120px, 100px) rotate(-270deg); opacity: 0.05; }
|
||||
}
|
||||
|
||||
@keyframes float3 {
|
||||
0%, 100% { transform: translate(0, 0) rotate(0deg); opacity: 0.03; }
|
||||
25% { transform: translate(-80px, -80px) rotate(45deg); opacity: 0.05; }
|
||||
50% { transform: translate(100px, 100px) rotate(135deg); opacity: 0.03; }
|
||||
75% { transform: translate(-100px, 80px) rotate(225deg); opacity: 0.04; }
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.app {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(-45deg, #0f0c29 0%, #302b63 25%, #24243e 50%, #302b63 75%, #0f0c29 100%);
|
||||
background-size: 400% 400%;
|
||||
animation: gradient-shift 15s ease infinite;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: radial-gradient(circle at 20% 50%, rgba(102, 126, 234, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 80%, rgba(118, 75, 162, 0.08) 0%, transparent 50%);
|
||||
animation: float1 20s ease-in-out infinite;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.app::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background: radial-gradient(circle, rgba(102, 126, 234, 0.12) 0%, transparent 70%);
|
||||
animation: float2 25s ease-in-out infinite;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.app > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%);
|
||||
}
|
||||
|
||||
.session-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 320px 1fr;
|
||||
min-height: 100vh;
|
||||
gap: 0;
|
||||
.new-session-btn {
|
||||
position: fixed;
|
||||
top: 2rem;
|
||||
right: 2rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: rgba(102, 126, 234, 0.8);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.2);
|
||||
backdrop-filter: blur(10px);
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.timeline-sidebar {
|
||||
background: rgba(48, 43, 99, 0.4);
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(20px);
|
||||
padding: 1.5rem;
|
||||
overflow-y: auto;
|
||||
max-height: 100vh;
|
||||
box-shadow: inset -1px 0 0 rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.sidebar-header h2 {
|
||||
font-size: 1.2rem;
|
||||
margin: 0;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.new-btn-small {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.2rem;
|
||||
background: rgba(102, 126, 234, 0.6);
|
||||
}
|
||||
|
||||
.new-btn-small:hover {
|
||||
background: rgba(102, 126, 234, 0.9);
|
||||
transform: rotate(20deg);
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 2rem;
|
||||
overflow-y: auto;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(102, 126, 234, 0.4);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(102, 126, 234, 0.6);
|
||||
}
|
||||
|
||||
/* Mobile responsive */
|
||||
@media (max-width: 1024px) {
|
||||
.session-layout {
|
||||
grid-template-columns: 250px 1fr;
|
||||
}
|
||||
|
||||
.timeline-sidebar {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.session-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.timeline-sidebar {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
max-height: none;
|
||||
}
|
||||
.new-session-btn:hover {
|
||||
background: rgba(102, 126, 234, 0.95);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 12px 40px rgba(102, 126, 234, 0.4);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -192,16 +192,7 @@ const removeFile = () => {
|
||||
.collaborative-input {
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
background: linear-gradient(
|
||||
-45deg,
|
||||
#0f0c29 0%,
|
||||
#302b63 25%,
|
||||
#24243e 50%,
|
||||
#302b63 75%,
|
||||
#0f0c29 100%
|
||||
);
|
||||
background-size: 400% 400%;
|
||||
animation: gradient-shift 15s ease infinite;
|
||||
background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -209,53 +200,20 @@ const removeFile = () => {
|
||||
.collaborative-input::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
top: -50%;
|
||||
right: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: radial-gradient(circle at 20% 50%, rgba(102, 126, 234, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 80%, rgba(118, 75, 162, 0.08) 0%, transparent 50%);
|
||||
animation: float1 20s ease-in-out infinite;
|
||||
background: radial-gradient(circle, rgba(102, 126, 234, 0.1) 0%, transparent 70%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
@keyframes gradient-shift {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float1 {
|
||||
0%, 100% {
|
||||
transform: translate(0, 0) rotate(0deg);
|
||||
opacity: 0.03;
|
||||
}
|
||||
25% {
|
||||
transform: translate(100px, -100px) rotate(90deg);
|
||||
opacity: 0.05;
|
||||
}
|
||||
50% {
|
||||
transform: translate(-50px, 150px) rotate(180deg);
|
||||
opacity: 0.03;
|
||||
}
|
||||
75% {
|
||||
transform: translate(-100px, -50px) rotate(270deg);
|
||||
opacity: 0.04;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.header {
|
||||
|
||||
@ -3,7 +3,6 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useCollaborationStore } from '../stores/collaboration'
|
||||
import { useWebSocket } from '../composables/useWebSocket'
|
||||
import DocumentViewer from './DocumentViewer.vue'
|
||||
import TimelinePanel from './TimelinePanel.vue'
|
||||
|
||||
const props = defineProps({
|
||||
sessionId: {
|
||||
@ -15,35 +14,21 @@ const props = defineProps({
|
||||
const emit = defineEmits(['session-completed'])
|
||||
|
||||
const collaborationStore = useCollaborationStore()
|
||||
const ws = useWebSocket(props.sessionId)
|
||||
const ws = useWebSocket(null, props.sessionId)
|
||||
|
||||
const isRunningRound = ref(false)
|
||||
const sessionStarted = ref(false)
|
||||
const isAutoRunning = ref(false)
|
||||
const autoRunTimeout = ref(null)
|
||||
const showTimeline = ref(false)
|
||||
const selectedVersion = ref(null)
|
||||
|
||||
const currentSession = computed(() => collaborationStore.currentSession)
|
||||
const currentDocument = computed(() => collaborationStore.currentDocument)
|
||||
const agents = computed(() => currentSession.value?.agents || [])
|
||||
const conversationHistory = computed(() => collaborationStore.conversationHistory)
|
||||
|
||||
// True convergence: ALL 7 agents must say no changes
|
||||
const hasConverged = computed(() => {
|
||||
if (conversationHistory.value.length === 0) return false
|
||||
const lastRound = conversationHistory.value[conversationHistory.value.length - 1]
|
||||
// Check if last round has no changes from ANY agent
|
||||
return !lastRound.agentsMadeChanges || (Array.isArray(lastRound.agentsMadeChanges) && lastRound.agentsMadeChanges.length === 0)
|
||||
})
|
||||
|
||||
// Count of agents in current session
|
||||
const agentCount = computed(() => agents.value?.length || 0)
|
||||
|
||||
// Check if all available agents have participated without changes
|
||||
const allAgentsConverged = computed(() => {
|
||||
if (conversationHistory.value.length === 0) return false
|
||||
const lastRound = conversationHistory.value[conversationHistory.value.length - 1]
|
||||
// All agents in this session must have zero changes
|
||||
return hasConverged.value && agentCount.value > 0
|
||||
return !lastRound.agentsMadeChanges || lastRound.agentsMadeChanges.length === 0
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
@ -62,10 +47,7 @@ onMounted(async () => {
|
||||
}
|
||||
}, 100)
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(messageInterval)
|
||||
if (autoRunTimeout.value) clearTimeout(autoRunTimeout.value)
|
||||
})
|
||||
onUnmounted(() => clearInterval(messageInterval))
|
||||
|
||||
// If session hasn't been started, start it
|
||||
if (currentSession.value?.status === 'created') {
|
||||
@ -80,50 +62,19 @@ const handleWebSocketMessage = (message) => {
|
||||
if (message.type === 'initial_document_created') {
|
||||
sessionStarted.value = true
|
||||
collaborationStore.updateDocumentFromMessage(message)
|
||||
// Start first auto-round after initial document
|
||||
scheduleNextRound(2000)
|
||||
} else if (message.type === 'document_modified') {
|
||||
collaborationStore.updateDocumentFromMessage(message)
|
||||
} else if (message.type === 'round_complete') {
|
||||
isRunningRound.value = false
|
||||
collaborationStore.updateDocumentFromMessage(message)
|
||||
|
||||
// Check convergence
|
||||
if (hasConverged.value && allAgentsConverged.value) {
|
||||
// All agents converged - auto-complete after delay
|
||||
isAutoRunning.value = false
|
||||
if (message.hasConverged) {
|
||||
// Auto-complete on convergence
|
||||
setTimeout(() => {
|
||||
completeSession()
|
||||
}, 2000)
|
||||
} else {
|
||||
// Schedule next round with delay
|
||||
scheduleNextRound(1500)
|
||||
}
|
||||
} else if (message.type === 'session_error') {
|
||||
console.error('Session error:', message.error)
|
||||
isRunningRound.value = false
|
||||
isAutoRunning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const scheduleNextRound = (delay) => {
|
||||
if (autoRunTimeout.value) clearTimeout(autoRunTimeout.value)
|
||||
isAutoRunning.value = true
|
||||
autoRunTimeout.value = setTimeout(() => {
|
||||
runNextRoundAuto()
|
||||
}, delay)
|
||||
}
|
||||
|
||||
const runNextRoundAuto = async () => {
|
||||
if (isRunningRound.value || hasConverged.value) return
|
||||
|
||||
isRunningRound.value = true
|
||||
try {
|
||||
await collaborationStore.runRound(props.sessionId)
|
||||
} catch (error) {
|
||||
console.error('Error running auto round:', error)
|
||||
isRunningRound.value = false
|
||||
isAutoRunning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,10 +86,20 @@ const startSession = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const runNextRound = async () => {
|
||||
if (isRunningRound.value || hasConverged.value) return
|
||||
|
||||
isRunningRound.value = true
|
||||
try {
|
||||
await collaborationStore.runRound(props.sessionId)
|
||||
} catch (error) {
|
||||
console.error('Error running round:', error)
|
||||
isRunningRound.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const completeSession = async () => {
|
||||
try {
|
||||
isAutoRunning.value = false
|
||||
if (autoRunTimeout.value) clearTimeout(autoRunTimeout.value)
|
||||
await collaborationStore.completeSession(props.sessionId)
|
||||
emit('session-completed')
|
||||
} catch (error) {
|
||||
@ -151,287 +112,113 @@ const downloadDocument = () => {
|
||||
const extension = format === 'md' ? 'md' : 'txt'
|
||||
collaborationStore.downloadDocument(`collaborative-document.${extension}`)
|
||||
}
|
||||
|
||||
const viewVersion = (versionNumber) => {
|
||||
selectedVersion.value = selectedVersion.value === versionNumber ? null : versionNumber
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="collaborative-session">
|
||||
<!-- Header with minimal controls -->
|
||||
<div class="session-header">
|
||||
<div class="header-content">
|
||||
<h1>Collaborative Design</h1>
|
||||
<h1>Design Session</h1>
|
||||
<p class="session-meta">
|
||||
<span>Session #{{ sessionId }}</span>
|
||||
<span class="badge" :class="{ active: sessionStarted, converged: allAgentsConverged }">
|
||||
{{ allAgentsConverged ? 'Converged' : sessionStarted ? 'Active' : 'Waiting' }}
|
||||
<span class="badge" :class="{ active: sessionStarted }">
|
||||
{{ sessionStarted ? 'Active' : 'Waiting' }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="header-actions">
|
||||
<button
|
||||
@click="runNextRound"
|
||||
:disabled="!sessionStarted || isRunningRound || hasConverged"
|
||||
class="btn btn-primary"
|
||||
>
|
||||
{{ isRunningRound ? 'Round in Progress...' : 'Next Review Round' }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="completeSession"
|
||||
:disabled="!sessionStarted"
|
||||
class="btn btn-secondary"
|
||||
>
|
||||
Complete Session
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="downloadDocument"
|
||||
:disabled="!currentDocument"
|
||||
class="download-btn"
|
||||
title="Download the document"
|
||||
class="btn btn-outline"
|
||||
>
|
||||
Download
|
||||
</button>
|
||||
|
||||
<div v-if="isAutoRunning || isRunningRound" class="auto-run-indicator">
|
||||
<span class="pulse"></span>
|
||||
Auto-running...
|
||||
</div>
|
||||
<button
|
||||
@click="showTimeline = !showTimeline"
|
||||
class="btn btn-outline"
|
||||
>
|
||||
Timeline
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status message -->
|
||||
<div v-if="allAgentsConverged" class="convergence-message">
|
||||
<span class="checkmark">[OK]</span>
|
||||
All {{ agentCount }} agents have reviewed and approved. Auto-completing...
|
||||
<!-- Status Message -->
|
||||
<div v-if="hasConverged" class="convergence-message">
|
||||
Convergence reached. All agents satisfied.
|
||||
</div>
|
||||
|
||||
<!-- Team agents display -->
|
||||
<div class="team-display">
|
||||
<h3>Team</h3>
|
||||
<div class="team-grid">
|
||||
<div v-for="agent in agents" :key="agent" class="team-member">
|
||||
<!-- Agent List -->
|
||||
<div class="agents-section">
|
||||
<h3>Team ({{ agents.length }} agents)</h3>
|
||||
<div class="agents-grid">
|
||||
<div v-for="agent in agents" :key="agent" class="agent-badge">
|
||||
{{ formatAgentName(agent) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Document Viewer -->
|
||||
<div class="document-section">
|
||||
<div class="document-header">
|
||||
<h2>Architecture Document</h2>
|
||||
<span v-if="currentDocument" class="doc-status">Live</span>
|
||||
<!-- Timeline View -->
|
||||
<div v-if="showTimeline" class="timeline-section">
|
||||
<h3>Modification Timeline</h3>
|
||||
<div class="timeline">
|
||||
<div v-for="(round, index) in conversationHistory" :key="index" class="timeline-item">
|
||||
<div class="timeline-marker">
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="round-title">Round {{ round.roundNumber }}</div>
|
||||
<div v-if="Array.isArray(round.agentsMadeChanges) && round.agentsMadeChanges.length > 0" class="agents-modified">
|
||||
Modified by: {{ round.agentsMadeChanges.join(', ') }}
|
||||
</div>
|
||||
<div v-else class="no-changes">
|
||||
No changes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Document Viewer -->
|
||||
<div class="document-section">
|
||||
<DocumentViewer
|
||||
:document="currentDocument || 'Waiting for initial document...'"
|
||||
:document="currentDocument"
|
||||
:format="currentSession?.documentFormat || 'md'"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Round Information -->
|
||||
<div class="round-info">
|
||||
<p>Current Round: <strong>{{ collaborationStore.currentRound }}</strong></p>
|
||||
<p v-if="conversationHistory.length > 0">
|
||||
Last Round: {{ conversationHistory.length }} agents reviewed
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
.collaborative-session {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.session-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5rem;
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.1);
|
||||
}
|
||||
|
||||
.header-content h1 {
|
||||
margin: 0;
|
||||
font-size: 1.8rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.session-meta {
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
font-size: 0.9rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.4rem 0.9rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 20px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.badge.active {
|
||||
background: rgba(102, 126, 234, 0.2);
|
||||
border-color: rgba(102, 126, 234, 0.4);
|
||||
color: rgba(102, 126, 234, 0.95);
|
||||
}
|
||||
|
||||
.badge.converged {
|
||||
background: rgba(76, 175, 80, 0.2);
|
||||
border-color: rgba(76, 175, 80, 0.4);
|
||||
color: rgba(76, 175, 80, 0.95);
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: rgba(102, 126, 234, 0.8);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.download-btn:hover:not(:disabled) {
|
||||
background: rgba(102, 126, 234, 0.95);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.download-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.auto-run-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: rgba(76, 175, 80, 0.15);
|
||||
border: 1px solid rgba(76, 175, 80, 0.3);
|
||||
border-radius: 10px;
|
||||
font-size: 0.85rem;
|
||||
color: rgba(76, 175, 80, 0.9);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.pulse {
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: rgba(76, 175, 80, 0.8);
|
||||
border-radius: 50%;
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
.convergence-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1.2rem;
|
||||
background: rgba(76, 175, 80, 0.1);
|
||||
border: 1px solid rgba(76, 175, 80, 0.3);
|
||||
border-radius: 12px;
|
||||
color: rgba(76, 175, 80, 0.95);
|
||||
font-weight: 500;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.checkmark {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: rgba(76, 175, 80, 0.3);
|
||||
border-radius: 50%;
|
||||
font-weight: 700;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.team-display {
|
||||
padding: 1rem;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.team-display h3 {
|
||||
margin: 0 0 0.75rem 0;
|
||||
font-size: 0.95rem;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.team-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.team-member {
|
||||
padding: 0.6rem 0.9rem;
|
||||
background: rgba(102, 126, 234, 0.12);
|
||||
border: 1px solid rgba(102, 126, 234, 0.25);
|
||||
border-radius: 8px;
|
||||
font-size: 0.85rem;
|
||||
color: rgba(102, 126, 234, 0.95);
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.team-member:hover {
|
||||
background: rgba(102, 126, 234, 0.2);
|
||||
border-color: rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.document-section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.document-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.75rem;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.document-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.doc-status {
|
||||
display: inline-block;
|
||||
padding: 0.3rem 0.7rem;
|
||||
background: rgba(76, 175, 80, 0.2);
|
||||
border: 1px solid rgba(76, 175, 80, 0.3);
|
||||
border-radius: 6px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: rgba(76, 175, 80, 0.9);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function formatAgentName(agent) {
|
||||
return agent
|
||||
@ -440,3 +227,227 @@ function formatAgentName(agent) {
|
||||
.join(' ')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.collaborative-session {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.session-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 2rem;
|
||||
padding: 1.5rem;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
border-radius: 20px;
|
||||
color: white;
|
||||
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.2);
|
||||
}
|
||||
|
||||
.header-content h1 {
|
||||
margin: 0;
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.session-meta {
|
||||
margin-top: 0.5rem;
|
||||
opacity: 0.9;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.75rem;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 20px;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.badge.active {
|
||||
background: rgba(76, 175, 80, 0.4);
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.75rem 1.25rem;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
color: #667eea;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary,
|
||||
.btn-outline {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: white;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.btn-secondary:hover:not(:disabled),
|
||||
.btn-outline:hover:not(:disabled) {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.convergence-message {
|
||||
padding: 1rem;
|
||||
background: rgba(76, 175, 80, 0.15);
|
||||
border: 1px solid rgba(76, 175, 80, 0.4);
|
||||
border-radius: 12px;
|
||||
color: rgba(76, 175, 80, 0.9);
|
||||
margin-bottom: 1.5rem;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.agents-section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.agents-section h3 {
|
||||
margin-bottom: 1rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.agents-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.agent-badge {
|
||||
padding: 0.75rem;
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
border: 1px solid rgba(102, 126, 234, 0.3);
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
font-size: 0.95rem;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.timeline-section {
|
||||
margin-bottom: 2rem;
|
||||
padding: 1.5rem;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
border-radius: 12px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.timeline-section h3 {
|
||||
margin-top: 0;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.timeline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
border-left: 3px solid rgba(102, 126, 234, 0.6);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.timeline-marker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background: rgba(102, 126, 234, 0.6);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.round-title {
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.agents-modified {
|
||||
font-size: 0.875rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.no-changes {
|
||||
font-size: 0.875rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.document-section {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.round-info {
|
||||
padding: 1rem;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.session-header {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.agents-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -64,12 +64,12 @@ function getStatusColor() {
|
||||
|
||||
function getStatusEmoji() {
|
||||
switch (quality.value) {
|
||||
case 'excellent': return 'O'
|
||||
case 'good': return 'O'
|
||||
case 'fair': return 'O'
|
||||
case 'poor': return 'O'
|
||||
case 'offline': return 'O'
|
||||
default: return 'O'
|
||||
case 'excellent': return '●'
|
||||
case 'good': return '●'
|
||||
case 'fair': return '●'
|
||||
case 'poor': return '●'
|
||||
case 'offline': return '●'
|
||||
default: return '●'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,275 +0,0 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { useCollaborationStore } from '../stores/collaboration'
|
||||
|
||||
const collaborationStore = useCollaborationStore()
|
||||
|
||||
const conversationHistory = computed(() => collaborationStore.conversationHistory)
|
||||
const currentRound = computed(() => collaborationStore.currentRound)
|
||||
|
||||
const allAgents = ['lead_architect', 'backend_engineer', 'frontend_engineer', 'ui_designer', 'devops_engineer', 'product_manager', 'security_specialist']
|
||||
|
||||
const getAgentStatus = (round) => {
|
||||
if (!round.agentsMadeChanges) return []
|
||||
return allAgents.map(agent => ({
|
||||
name: formatAgentName(agent),
|
||||
made_changes: round.agentsMadeChanges.includes(agent) || round.agentsMadeChanges.some(a => a.includes(agent))
|
||||
}))
|
||||
}
|
||||
|
||||
const roundProgress = computed(() => {
|
||||
if (conversationHistory.value.length === 0) return 0
|
||||
return Math.round((conversationHistory.value.length / 10) * 100)
|
||||
})
|
||||
|
||||
function formatAgentName(agent) {
|
||||
return agent
|
||||
.split('_')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="timeline-panel">
|
||||
<!-- Progress Bar -->
|
||||
<div class="progress-section">
|
||||
<div class="progress-header">
|
||||
<span class="progress-label">Session Progress</span>
|
||||
<span class="progress-percent">{{ roundProgress }}%</span>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" :style="{ width: roundProgress + '%' }"></div>
|
||||
</div>
|
||||
<div class="round-counter">Round {{ currentRound }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Timeline -->
|
||||
<div class="timeline-list">
|
||||
<div
|
||||
v-for="(round, index) in conversationHistory"
|
||||
:key="index"
|
||||
class="timeline-entry"
|
||||
>
|
||||
<div class="entry-header">
|
||||
<div class="round-number">{{ index + 1 }}</div>
|
||||
<div class="round-label">Round {{ round.roundNumber }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Agent status for this round -->
|
||||
<div class="agent-list">
|
||||
<div
|
||||
v-for="agent in getAgentStatus(round)"
|
||||
:key="agent.name"
|
||||
class="agent-item"
|
||||
:class="{ active: agent.made_changes }"
|
||||
:title="agent.made_changes ? 'Made changes' : 'No changes'"
|
||||
>
|
||||
<span class="agent-dot" :class="{ changed: agent.made_changes }"></span>
|
||||
<span class="agent-short">{{ agent.name.split(' ')[0].slice(0, 1) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Changes summary -->
|
||||
<div v-if="round.agentsMadeChanges && round.agentsMadeChanges.length > 0" class="changes-count">
|
||||
{{ round.agentsMadeChanges.length }} agent{{ round.agentsMadeChanges.length !== 1 ? 's' : '' }} modified
|
||||
</div>
|
||||
<div v-else class="no-changes-text">No changes</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty state -->
|
||||
<div v-if="conversationHistory.length === 0" class="empty-state">
|
||||
<p>Waiting for first round...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.timeline-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
padding: 1rem;
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
border: 1px solid rgba(102, 126, 234, 0.3);
|
||||
border-radius: 12px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.progress-percent {
|
||||
color: var(--primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 6px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
border-radius: 3px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.round-counter {
|
||||
text-align: center;
|
||||
font-size: 0.875rem;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.timeline-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.timeline-entry {
|
||||
padding: 0.875rem;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(102, 126, 234, 0.2);
|
||||
border-radius: 10px;
|
||||
backdrop-filter: blur(10px);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.timeline-entry:hover {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border-color: rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.entry-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.round-number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: rgba(102, 126, 234, 0.5);
|
||||
border-radius: 50%;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.round-label {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.agent-list {
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
margin-bottom: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.agent-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 6px;
|
||||
font-size: 0.7rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
cursor: help;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.agent-item.active {
|
||||
background: rgba(76, 175, 80, 0.2);
|
||||
color: rgba(76, 175, 80, 0.9);
|
||||
border: 1px solid rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
.agent-dot {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.agent-dot.changed {
|
||||
background: rgba(76, 175, 80, 0.8);
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
.agent-short {
|
||||
font-weight: 600;
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
|
||||
.changes-count {
|
||||
font-size: 0.75rem;
|
||||
color: rgba(76, 175, 80, 0.8);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.no-changes-text {
|
||||
font-size: 0.75rem;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
text-align: center;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
.timeline-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.timeline-list::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.timeline-list::-webkit-scrollbar-thumb {
|
||||
background: rgba(102, 126, 234, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.timeline-list::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(102, 126, 234, 0.5);
|
||||
}
|
||||
</style>
|
||||
@ -1,6 +1,6 @@
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
|
||||
export function useWebSocket(sessionId = null) {
|
||||
export function useWebSocket(debateId = null, sessionId = null) {
|
||||
const ws = ref(null)
|
||||
const connected = ref(false)
|
||||
const messages = ref([])
|
||||
@ -9,7 +9,9 @@ export function useWebSocket(sessionId = null) {
|
||||
|
||||
function connect() {
|
||||
let url = WS_URL
|
||||
if (sessionId) {
|
||||
if (debateId) {
|
||||
url += `?debateId=${debateId}`
|
||||
} else if (sessionId) {
|
||||
url += `?sessionId=${sessionId}`
|
||||
}
|
||||
|
||||
|
||||
@ -1,136 +1,79 @@
|
||||
:root {
|
||||
--primary: #667eea;
|
||||
--primary-dark: #5568d3;
|
||||
--secondary: #764ba2;
|
||||
--accent: #f093fb;
|
||||
--dark-bg: #0f0c29;
|
||||
--mid-bg: #302b63;
|
||||
--light-bg: #24243e;
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: rgba(255, 255, 255, 0.7);
|
||||
--glass-bg: rgba(255, 255, 255, 0.05);
|
||||
--glass-border: rgba(255, 255, 255, 0.1);
|
||||
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
line-height: 1.6;
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
color-scheme: dark;
|
||||
color: var(--text-primary);
|
||||
background-color: var(--dark-bg);
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary);
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--accent);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
h1 { font-size: 2.5em; }
|
||||
h2 { font-size: 2em; }
|
||||
h3 { font-size: 1.5em; }
|
||||
|
||||
button {
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--glass-border);
|
||||
padding: 0.75em 1.5em;
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background: rgba(102, 126, 234, 0.8);
|
||||
color: var(--text-primary);
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.1);
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
button:hover:not(:disabled) {
|
||||
background: rgba(102, 126, 234, 0.95);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 12px 40px rgba(102, 126, 234, 0.3);
|
||||
border-color: var(--glass-border);
|
||||
}
|
||||
|
||||
button:active:not(:disabled) {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
button.secondary {
|
||||
background: rgba(118, 75, 162, 0.6);
|
||||
}
|
||||
|
||||
button.secondary:hover:not(:disabled) {
|
||||
background: rgba(118, 75, 162, 0.8);
|
||||
}
|
||||
|
||||
button.outline {
|
||||
background: transparent;
|
||||
border: 1px solid var(--primary);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
button.outline:hover:not(:disabled) {
|
||||
background: rgba(102, 126, 234, 0.2);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
input, textarea, select {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid var(--glass-border);
|
||||
color: var(--text-primary);
|
||||
padding: 0.75em 1em;
|
||||
border-radius: 8px;
|
||||
font-family: inherit;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
input:focus, textarea:focus, select:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 20px rgba(102, 126, 234, 0.3);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user