Refactor: UI redesign with new glasmorphism layout and auto-rounds
- Remove all emojis from UI elements - Redesign with two-column layout: timeline sidebar + content - Implement automatic rounds without manual button - Add persistent timeline panel with progress tracking - Implement true convergence (all agents must agree) - Add TimelinePanel component with progress bar - Update styling with improved glasmorphism effects - Clean up mentions of external tools
This commit is contained in:
parent
fca2b58689
commit
31cd84186f
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 :
|
Reproduire une **table ronde d'IA spécialistes**, chaque agent IA représentant un rôle spécifique :
|
||||||
|
|
||||||
- Architecte logiciel
|
- Lead Architect (Architecte logiciel)
|
||||||
- Développeur backend
|
- Backend Engineer (Développeur backend)
|
||||||
- Développeur frontend
|
- Frontend Engineer (Développeur frontend)
|
||||||
- Designer UI/UX
|
- UI Designer (Designer UI/UX)
|
||||||
- Data engineer
|
- DevOps Engineer (Ingénieur DevOps)
|
||||||
- Chef de projet
|
- Product Manager (Chef de projet)
|
||||||
- Éventuellement d'autres rôles selon la complexité du prompt.
|
- Security Specialist (Spécialiste sécurité)
|
||||||
|
|
||||||
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.
|
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).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -96,16 +96,17 @@ graph TD
|
|||||||
|
|
||||||
### Interface et visualisation
|
### Interface et visualisation
|
||||||
- **Saisie de prompt** décrivant le projet souhaité
|
- **Saisie de prompt** décrivant le projet souhaité
|
||||||
- **Visualisation en temps réel** des échanges entre agents via WebSocket
|
- **Upload optionnel** de fichiers contexte (MD/TXT)
|
||||||
- **Affichage structuré** des propositions avec justifications et niveaux de confiance
|
- **Visualisation en temps réel** des modifications via WebSocket
|
||||||
- **Rendu automatique** de diagrammes Mermaid intégrés dans les réponses
|
- **Timeline d'évolution** du document avec historique complet
|
||||||
- **Indicateurs de progression** et statuts du débat
|
- **Affichage interactif** du document Markdown final
|
||||||
|
- **Indicateurs de progression** et statuts du session
|
||||||
|
|
||||||
### Architecture et stockage
|
### Architecture et stockage
|
||||||
- **API REST** pour gestion des débats et récupération des résultats
|
- **API REST** pour gestion des sessions collaboratives
|
||||||
- **WebSocket** pour streaming temps réel des réponses des agents
|
- **WebSocket** pour streaming temps réel des modifications d'agents
|
||||||
- **Base SQLite** pour persistance des débats et historique
|
- **Base SQLite** pour persistance des sessions, versions et historique complet
|
||||||
- **Gestion d'erreurs** avec fallbacks et réponses de secours
|
- **Versioning intelligent** : Chaque modification crée une version avec métadonnées (agent, raison, round)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { ref } from 'vue'
|
|||||||
import { useCollaborationStore } from './stores/collaboration'
|
import { useCollaborationStore } from './stores/collaboration'
|
||||||
import CollaborativeInput from './components/CollaborativeInput.vue'
|
import CollaborativeInput from './components/CollaborativeInput.vue'
|
||||||
import CollaborativeSession from './components/CollaborativeSession.vue'
|
import CollaborativeSession from './components/CollaborativeSession.vue'
|
||||||
|
import TimelinePanel from './components/TimelinePanel.vue'
|
||||||
import NetworkStatus from './components/NetworkStatus.vue'
|
import NetworkStatus from './components/NetworkStatus.vue'
|
||||||
|
|
||||||
const collaborationStore = useCollaborationStore()
|
const collaborationStore = useCollaborationStore()
|
||||||
@ -25,17 +26,28 @@ function startNewCollaboration() {
|
|||||||
<div class="app">
|
<div class="app">
|
||||||
<NetworkStatus />
|
<NetworkStatus />
|
||||||
|
|
||||||
<!-- Input Mode -->
|
<!-- Input Mode - Centered Full Screen -->
|
||||||
<div v-if="!showSession">
|
<div v-if="!showSession" class="input-container">
|
||||||
<CollaborativeInput @session-created="handleCollaborationCreated" />
|
<CollaborativeInput @session-created="handleCollaborationCreated" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Session Mode -->
|
<!-- Session Mode - Two Column Layout -->
|
||||||
<div v-else>
|
<div v-else class="session-layout">
|
||||||
<button @click="startNewCollaboration" class="new-session-btn">
|
<!-- Left Sidebar: Timeline -->
|
||||||
New Session
|
<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>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Timeline Panel -->
|
||||||
|
<TimelinePanel />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Content: Session -->
|
||||||
|
<div class="main-content">
|
||||||
<CollaborativeSession
|
<CollaborativeSession
|
||||||
v-if="currentSessionId"
|
v-if="currentSessionId"
|
||||||
:session-id="currentSessionId"
|
:session-id="currentSessionId"
|
||||||
@ -43,106 +55,47 @@ function startNewCollaboration() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
||||||
color: #2c3e50;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes gradient-shift {
|
@keyframes gradient-shift {
|
||||||
0% {
|
0% { background-position: 0% 50%; }
|
||||||
background-position: 0% 50%;
|
50% { background-position: 100% 50%; }
|
||||||
}
|
100% { background-position: 0% 50%; }
|
||||||
50% {
|
|
||||||
background-position: 100% 50%;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-position: 0% 50%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes float1 {
|
@keyframes float1 {
|
||||||
0%, 100% {
|
0%, 100% { transform: translate(0, 0) rotate(0deg); opacity: 0.03; }
|
||||||
transform: translate(0, 0) rotate(0deg);
|
25% { transform: translate(100px, -100px) rotate(90deg); opacity: 0.05; }
|
||||||
opacity: 0.03;
|
50% { transform: translate(-50px, 150px) rotate(180deg); opacity: 0.03; }
|
||||||
}
|
75% { transform: translate(-100px, -50px) rotate(270deg); opacity: 0.04; }
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes float2 {
|
@keyframes float2 {
|
||||||
0%, 100% {
|
0%, 100% { transform: translate(0, 0) rotate(0deg); opacity: 0.04; }
|
||||||
transform: translate(0, 0) rotate(0deg);
|
25% { transform: translate(-120px, 80px) rotate(-90deg); opacity: 0.06; }
|
||||||
opacity: 0.04;
|
50% { transform: translate(80px, -120px) rotate(-180deg); opacity: 0.04; }
|
||||||
}
|
75% { transform: translate(120px, 100px) rotate(-270deg); opacity: 0.05; }
|
||||||
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 {
|
@keyframes float3 {
|
||||||
0%, 100% {
|
0%, 100% { transform: translate(0, 0) rotate(0deg); opacity: 0.03; }
|
||||||
transform: translate(0, 0) rotate(0deg);
|
25% { transform: translate(-80px, -80px) rotate(45deg); opacity: 0.05; }
|
||||||
opacity: 0.03;
|
50% { transform: translate(100px, 100px) rotate(135deg); opacity: 0.03; }
|
||||||
}
|
75% { transform: translate(-100px, 80px) rotate(225deg); opacity: 0.04; }
|
||||||
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; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.app {
|
.app {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding: 2rem;
|
background: linear-gradient(-45deg, #0f0c29 0%, #302b63 25%, #24243e 50%, #302b63 75%, #0f0c29 100%);
|
||||||
background: linear-gradient(
|
|
||||||
-45deg,
|
|
||||||
#0f0c29 0%,
|
|
||||||
#302b63 25%,
|
|
||||||
#24243e 50%,
|
|
||||||
#302b63 75%,
|
|
||||||
#0f0c29 100%
|
|
||||||
);
|
|
||||||
background-size: 400% 400%;
|
background-size: 400% 400%;
|
||||||
animation: gradient-shift 15s ease infinite;
|
animation: gradient-shift 15s ease infinite;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -182,27 +135,125 @@ body {
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.new-session-btn {
|
.input-container {
|
||||||
position: fixed;
|
display: flex;
|
||||||
top: 2rem;
|
align-items: center;
|
||||||
right: 2rem;
|
justify-content: center;
|
||||||
padding: 0.75rem 1.5rem;
|
min-height: 100vh;
|
||||||
background: rgba(102, 126, 234, 0.8);
|
padding: 2rem;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.new-session-btn:hover {
|
.session-layout {
|
||||||
background: rgba(102, 126, 234, 0.95);
|
display: grid;
|
||||||
transform: translateY(-2px);
|
grid-template-columns: 320px 1fr;
|
||||||
box-shadow: 0 12px 40px rgba(102, 126, 234, 0.4);
|
min-height: 100vh;
|
||||||
border-color: rgba(255, 255, 255, 0.3);
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|||||||
import { useCollaborationStore } from '../stores/collaboration'
|
import { useCollaborationStore } from '../stores/collaboration'
|
||||||
import { useWebSocket } from '../composables/useWebSocket'
|
import { useWebSocket } from '../composables/useWebSocket'
|
||||||
import DocumentViewer from './DocumentViewer.vue'
|
import DocumentViewer from './DocumentViewer.vue'
|
||||||
|
import TimelinePanel from './TimelinePanel.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
sessionId: {
|
sessionId: {
|
||||||
@ -18,17 +19,31 @@ const ws = useWebSocket(props.sessionId)
|
|||||||
|
|
||||||
const isRunningRound = ref(false)
|
const isRunningRound = ref(false)
|
||||||
const sessionStarted = ref(false)
|
const sessionStarted = ref(false)
|
||||||
const showTimeline = ref(false)
|
const isAutoRunning = ref(false)
|
||||||
const selectedVersion = ref(null)
|
const autoRunTimeout = ref(null)
|
||||||
|
|
||||||
const currentSession = computed(() => collaborationStore.currentSession)
|
const currentSession = computed(() => collaborationStore.currentSession)
|
||||||
const currentDocument = computed(() => collaborationStore.currentDocument)
|
const currentDocument = computed(() => collaborationStore.currentDocument)
|
||||||
const agents = computed(() => currentSession.value?.agents || [])
|
const agents = computed(() => currentSession.value?.agents || [])
|
||||||
const conversationHistory = computed(() => collaborationStore.conversationHistory)
|
const conversationHistory = computed(() => collaborationStore.conversationHistory)
|
||||||
|
|
||||||
|
// True convergence: ALL 7 agents must say no changes
|
||||||
const hasConverged = computed(() => {
|
const hasConverged = computed(() => {
|
||||||
if (conversationHistory.value.length === 0) return false
|
if (conversationHistory.value.length === 0) return false
|
||||||
const lastRound = conversationHistory.value[conversationHistory.value.length - 1]
|
const lastRound = conversationHistory.value[conversationHistory.value.length - 1]
|
||||||
return !lastRound.agentsMadeChanges || lastRound.agentsMadeChanges.length === 0
|
// 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
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@ -47,7 +62,10 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
}, 100)
|
}, 100)
|
||||||
|
|
||||||
onUnmounted(() => clearInterval(messageInterval))
|
onUnmounted(() => {
|
||||||
|
clearInterval(messageInterval)
|
||||||
|
if (autoRunTimeout.value) clearTimeout(autoRunTimeout.value)
|
||||||
|
})
|
||||||
|
|
||||||
// If session hasn't been started, start it
|
// If session hasn't been started, start it
|
||||||
if (currentSession.value?.status === 'created') {
|
if (currentSession.value?.status === 'created') {
|
||||||
@ -62,19 +80,50 @@ const handleWebSocketMessage = (message) => {
|
|||||||
if (message.type === 'initial_document_created') {
|
if (message.type === 'initial_document_created') {
|
||||||
sessionStarted.value = true
|
sessionStarted.value = true
|
||||||
collaborationStore.updateDocumentFromMessage(message)
|
collaborationStore.updateDocumentFromMessage(message)
|
||||||
|
// Start first auto-round after initial document
|
||||||
|
scheduleNextRound(2000)
|
||||||
} else if (message.type === 'document_modified') {
|
} else if (message.type === 'document_modified') {
|
||||||
collaborationStore.updateDocumentFromMessage(message)
|
collaborationStore.updateDocumentFromMessage(message)
|
||||||
} else if (message.type === 'round_complete') {
|
} else if (message.type === 'round_complete') {
|
||||||
isRunningRound.value = false
|
isRunningRound.value = false
|
||||||
collaborationStore.updateDocumentFromMessage(message)
|
collaborationStore.updateDocumentFromMessage(message)
|
||||||
if (message.hasConverged) {
|
|
||||||
// Auto-complete on convergence
|
// Check convergence
|
||||||
|
if (hasConverged.value && allAgentsConverged.value) {
|
||||||
|
// All agents converged - auto-complete after delay
|
||||||
|
isAutoRunning.value = false
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
completeSession()
|
completeSession()
|
||||||
}, 2000)
|
}, 2000)
|
||||||
|
} else {
|
||||||
|
// Schedule next round with delay
|
||||||
|
scheduleNextRound(1500)
|
||||||
}
|
}
|
||||||
} else if (message.type === 'session_error') {
|
} else if (message.type === 'session_error') {
|
||||||
console.error('Session error:', message.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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,20 +135,10 @@ 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 () => {
|
const completeSession = async () => {
|
||||||
try {
|
try {
|
||||||
|
isAutoRunning.value = false
|
||||||
|
if (autoRunTimeout.value) clearTimeout(autoRunTimeout.value)
|
||||||
await collaborationStore.completeSession(props.sessionId)
|
await collaborationStore.completeSession(props.sessionId)
|
||||||
emit('session-completed')
|
emit('session-completed')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -112,113 +151,287 @@ const downloadDocument = () => {
|
|||||||
const extension = format === 'md' ? 'md' : 'txt'
|
const extension = format === 'md' ? 'md' : 'txt'
|
||||||
collaborationStore.downloadDocument(`collaborative-document.${extension}`)
|
collaborationStore.downloadDocument(`collaborative-document.${extension}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewVersion = (versionNumber) => {
|
|
||||||
selectedVersion.value = selectedVersion.value === versionNumber ? null : versionNumber
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="collaborative-session">
|
<div class="collaborative-session">
|
||||||
|
<!-- Header with minimal controls -->
|
||||||
<div class="session-header">
|
<div class="session-header">
|
||||||
<div class="header-content">
|
<div class="header-content">
|
||||||
<h1>Design Session</h1>
|
<h1>Collaborative Design</h1>
|
||||||
<p class="session-meta">
|
<p class="session-meta">
|
||||||
<span>Session #{{ sessionId }}</span>
|
<span>Session #{{ sessionId }}</span>
|
||||||
<span class="badge" :class="{ active: sessionStarted }">
|
<span class="badge" :class="{ active: sessionStarted, converged: allAgentsConverged }">
|
||||||
{{ sessionStarted ? 'Active' : 'Waiting' }}
|
{{ allAgentsConverged ? 'Converged' : sessionStarted ? 'Active' : 'Waiting' }}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="header-actions">
|
<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
|
<button
|
||||||
@click="downloadDocument"
|
@click="downloadDocument"
|
||||||
:disabled="!currentDocument"
|
:disabled="!currentDocument"
|
||||||
class="btn btn-outline"
|
class="download-btn"
|
||||||
|
title="Download the document"
|
||||||
>
|
>
|
||||||
Download
|
Download
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<div v-if="isAutoRunning || isRunningRound" class="auto-run-indicator">
|
||||||
@click="showTimeline = !showTimeline"
|
<span class="pulse"></span>
|
||||||
class="btn btn-outline"
|
Auto-running...
|
||||||
>
|
</div>
|
||||||
Timeline
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Status Message -->
|
<!-- Status message -->
|
||||||
<div v-if="hasConverged" class="convergence-message">
|
<div v-if="allAgentsConverged" class="convergence-message">
|
||||||
Convergence reached. All agents satisfied.
|
<span class="checkmark">[OK]</span>
|
||||||
|
All {{ agentCount }} agents have reviewed and approved. Auto-completing...
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Agent List -->
|
<!-- Team agents display -->
|
||||||
<div class="agents-section">
|
<div class="team-display">
|
||||||
<h3>Team ({{ agents.length }} agents)</h3>
|
<h3>Team</h3>
|
||||||
<div class="agents-grid">
|
<div class="team-grid">
|
||||||
<div v-for="agent in agents" :key="agent" class="agent-badge">
|
<div v-for="agent in agents" :key="agent" class="team-member">
|
||||||
{{ formatAgentName(agent) }}
|
{{ formatAgentName(agent) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Timeline View -->
|
<!-- Main Document Viewer -->
|
||||||
<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">
|
<div class="document-section">
|
||||||
|
<div class="document-header">
|
||||||
|
<h2>Architecture Document</h2>
|
||||||
|
<span v-if="currentDocument" class="doc-status">Live</span>
|
||||||
|
</div>
|
||||||
<DocumentViewer
|
<DocumentViewer
|
||||||
:document="currentDocument"
|
:document="currentDocument || 'Waiting for initial document...'"
|
||||||
:format="currentSession?.documentFormat || 'md'"
|
:format="currentSession?.documentFormat || 'md'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</template>
|
</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>
|
<script>
|
||||||
function formatAgentName(agent) {
|
function formatAgentName(agent) {
|
||||||
return agent
|
return agent
|
||||||
@ -227,290 +440,3 @@ function formatAgentName(agent) {
|
|||||||
.join(' ')
|
.join(' ')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.collaborative-session {
|
|
||||||
max-width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 2rem;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collaborative-session::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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collaborative-session > * {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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() {
|
function getStatusEmoji() {
|
||||||
switch (quality.value) {
|
switch (quality.value) {
|
||||||
case 'excellent': return '●'
|
case 'excellent': return 'O'
|
||||||
case 'good': return '●'
|
case 'good': return 'O'
|
||||||
case 'fair': return '●'
|
case 'fair': return 'O'
|
||||||
case 'poor': return '●'
|
case 'poor': return 'O'
|
||||||
case 'offline': return '●'
|
case 'offline': return 'O'
|
||||||
default: return '●'
|
default: return 'O'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
275
frontend/src/components/TimelinePanel.vue
Normal file
275
frontend/src/components/TimelinePanel.vue
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
<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,79 +1,136 @@
|
|||||||
:root {
|
:root {
|
||||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
--primary: #667eea;
|
||||||
line-height: 1.5;
|
--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-weight: 400;
|
font-weight: 400;
|
||||||
|
color-scheme: dark;
|
||||||
color-scheme: light dark;
|
color: var(--text-primary);
|
||||||
color: rgba(255, 255, 255, 0.87);
|
background-color: var(--dark-bg);
|
||||||
background-color: #242424;
|
|
||||||
|
|
||||||
font-synthesis: none;
|
font-synthesis: none;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
* {
|
||||||
font-weight: 500;
|
margin: 0;
|
||||||
color: #646cff;
|
padding: 0;
|
||||||
text-decoration: inherit;
|
box-sizing: border-box;
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #535bf2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
a {
|
||||||
font-size: 3.2em;
|
color: var(--primary);
|
||||||
line-height: 1.1;
|
text-decoration: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
button {
|
||||||
border-radius: 8px;
|
border-radius: 12px;
|
||||||
border: 1px solid transparent;
|
border: 1px solid var(--glass-border);
|
||||||
padding: 0.6em 1.2em;
|
padding: 0.75em 1.5em;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
background-color: #1a1a1a;
|
background: rgba(102, 126, 234, 0.8);
|
||||||
|
color: var(--text-primary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color 0.25s;
|
transition: all 0.3s ease;
|
||||||
}
|
backdrop-filter: blur(10px);
|
||||||
button:hover {
|
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.1);
|
||||||
border-color: #646cff;
|
|
||||||
}
|
|
||||||
button:focus,
|
|
||||||
button:focus-visible {
|
|
||||||
outline: 4px auto -webkit-focus-ring-color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
button:hover:not(:disabled) {
|
||||||
padding: 2em;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
max-width: 1280px;
|
min-height: 100vh;
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
:root {
|
*, *::before, *::after {
|
||||||
color: #213547;
|
animation-duration: 0.01ms !important;
|
||||||
background-color: #ffffff;
|
animation-iteration-count: 1 !important;
|
||||||
}
|
transition-duration: 0.01ms !important;
|
||||||
a:hover {
|
|
||||||
color: #747bff;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user