Update et upgrade by codex

This commit is contained in:
Augustin ROUX 2025-10-24 13:09:18 +02:00
parent 130e21c867
commit 0179bf75f2
4 changed files with 162 additions and 31 deletions

View File

@ -10,24 +10,22 @@ const prompt = ref('')
const contextFile = ref(null) const contextFile = ref(null)
const agentCount = ref(7) const agentCount = ref(7)
const aiProvider = ref('mistral') const aiProvider = ref('mistral')
const documentFormat = ref('md')
const isCreating = ref(false) const isCreating = ref(false)
const previousSessions = ref([])
const loadingPreviousSessions = ref(false)
const showAllSessions = ref(false) const showAllSessions = ref(false)
const showArchives = ref(false)
const sessionStatusFilter = ref('all') // 'all', 'completed', 'ongoing', 'created' const sessionStatusFilter = ref('all') // 'all', 'completed', 'ongoing', 'created'
const searchQuery = ref('') const searchQuery = ref('')
const isFetchingSessions = ref(false)
onMounted(async () => { onMounted(async () => {
loadingPreviousSessions.value = true if (collaborationStore.sessionsLoaded.value) return
isFetchingSessions.value = true
try { try {
const response = await fetch('/api/collaborate') await collaborationStore.fetchSessions()
const data = await response.json()
previousSessions.value = data.sessions || []
} catch (error) { } catch (error) {
console.error('Error loading previous sessions:', error) console.error('Error loading previous sessions:', error)
} finally { } finally {
loadingPreviousSessions.value = false isFetchingSessions.value = false
} }
}) })
@ -35,6 +33,8 @@ const handleOpenSession = (sessionId) => {
emit('session-created', { sessionId }) emit('session-created', { sessionId })
} }
const sessions = computed(() => collaborationStore.sessions.value)
const agentOptions = computed(() => { const agentOptions = computed(() => {
return Array.from({ length: 48 }, (_, i) => ({ return Array.from({ length: 48 }, (_, i) => ({
value: i + 3, value: i + 3,
@ -43,7 +43,7 @@ const agentOptions = computed(() => {
}) })
const filteredSessions = computed(() => { const filteredSessions = computed(() => {
let filtered = previousSessions.value let filtered = sessions.value
if (sessionStatusFilter.value !== 'all') { if (sessionStatusFilter.value !== 'all') {
filtered = filtered.filter(s => s.status === sessionStatusFilter.value) filtered = filtered.filter(s => s.status === sessionStatusFilter.value)
@ -65,11 +65,11 @@ const displayedSessions = computed(() => {
}) })
const completedCount = computed(() => { const completedCount = computed(() => {
return previousSessions.value.filter(s => s.status === 'completed').length return sessions.value.filter(s => s.status === 'completed').length
}) })
const ongoingCount = computed(() => { const ongoingCount = computed(() => {
return previousSessions.value.filter(s => s.status === 'ongoing').length return sessions.value.filter(s => s.status === 'ongoing').length
}) })
const handleFileSelect = (event) => { const handleFileSelect = (event) => {
@ -108,7 +108,7 @@ const handleCreateSession = async () => {
// Always use 'md' format for output // Always use 'md' format for output
const session = await collaborationStore.createSession( const session = await collaborationStore.createSession(
finalPrompt, finalPrompt,
'md', documentFormat.value,
agentCount.value, agentCount.value,
aiProvider.value aiProvider.value
) )
@ -120,6 +120,7 @@ const handleCreateSession = async () => {
contextFile.value = null contextFile.value = null
agentCount.value = 7 agentCount.value = 7
aiProvider.value = 'mistral' aiProvider.value = 'mistral'
documentFormat.value = 'md'
} catch (error) { } catch (error) {
alert(`Error creating session: ${collaborationStore.error}`) alert(`Error creating session: ${collaborationStore.error}`)
} finally { } finally {
@ -150,7 +151,11 @@ const removeFile = () => {
</header> </header>
<!-- Quick Access Section --> <!-- Quick Access Section -->
<div v-if="previousSessions.length > 0" class="quick-access-section"> <div v-if="isFetchingSessions" class="loading-sessions">
Loading previous sessions...
</div>
<div v-else-if="sessions.length > 0" class="quick-access-section">
<div class="stats-bar"> <div class="stats-bar">
<div class="stat"> <div class="stat">
<span class="stat-label">Completed</span> <span class="stat-label">Completed</span>
@ -162,7 +167,7 @@ const removeFile = () => {
</div> </div>
<div class="stat"> <div class="stat">
<span class="stat-label">Total</span> <span class="stat-label">Total</span>
<span class="stat-value">{{ previousSessions.length }}</span> <span class="stat-value">{{ sessions.length }}</span>
</div> </div>
</div> </div>
@ -183,7 +188,7 @@ const removeFile = () => {
class="filter-btn" class="filter-btn"
:class="{ active: sessionStatusFilter === 'all' }" :class="{ active: sessionStatusFilter === 'all' }"
> >
All Sessions ({{ previousSessions.length }}) All Sessions ({{ sessions.length }})
</button> </button>
<button <button
@click="sessionStatusFilter = 'completed'" @click="sessionStatusFilter = 'completed'"
@ -237,7 +242,7 @@ const removeFile = () => {
</div> </div>
<!-- Divider --> <!-- Divider -->
<div v-if="previousSessions.length > 0" class="divider"> <div v-if="sessions.length > 0" class="divider">
<span>Or start a new session</span> <span>Or start a new session</span>
</div> </div>
@ -312,6 +317,14 @@ const removeFile = () => {
</select> </select>
<p class="hint">Choose the AI model for specialists.</p> <p class="hint">Choose the AI model for specialists.</p>
</div> </div>
<div class="form-group">
<label for="format" class="label">Document Format</label>
<select v-model="documentFormat" id="format" class="select">
<option value="md">Markdown (.md)</option>
<option value="txt">Plain Text (.txt)</option>
</select>
<p class="hint">Markdown recommended for structured documents.</p>
</div>
</div> </div>
<!-- Info Box --> <!-- Info Box -->
@ -624,6 +637,17 @@ const removeFile = () => {
cursor: not-allowed; cursor: not-allowed;
} }
.loading-sessions {
margin-bottom: 2rem;
padding: 1rem;
text-align: center;
color: rgba(255, 255, 255, 0.7);
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 12px;
backdrop-filter: blur(8px);
}
.file-input-wrapper { .file-input-wrapper {
position: relative; position: relative;
margin-bottom: 1rem; margin-bottom: 1rem;

View File

@ -80,11 +80,19 @@ const handleWebSocketMessage = (message) => {
if (message.type === 'initial_document_created') { if (message.type === 'initial_document_created') {
sessionStarted.value = true sessionStarted.value = true
collaborationStore.currentDocument = message.content collaborationStore.currentDocument = message.content
collaborationStore.currentRound = 0
if (collaborationStore.currentSession) {
collaborationStore.currentSession.status = 'ongoing'
collaborationStore.currentSession.currentDocument = message.content
}
currentWorkingAgent.value = null currentWorkingAgent.value = null
currentAgentThinking.value = '' currentAgentThinking.value = ''
scheduleNextRound(2000) scheduleNextRound(2000)
} else if (message.type === 'document_modified') { } else if (message.type === 'document_modified') {
collaborationStore.currentDocument = message.content collaborationStore.currentDocument = message.content
if (collaborationStore.currentSession) {
collaborationStore.currentSession.currentDocument = message.content
}
} else if (message.type === 'agent_working') { } else if (message.type === 'agent_working') {
currentWorkingAgent.value = message.agentName currentWorkingAgent.value = message.agentName
currentAgentThinking.value = '' currentAgentThinking.value = ''
@ -92,11 +100,17 @@ const handleWebSocketMessage = (message) => {
currentAgentThinking.value = message.thinking || '' currentAgentThinking.value = message.thinking || ''
} else if (message.type === 'round_complete') { } else if (message.type === 'round_complete') {
isRunningRound.value = false isRunningRound.value = false
collaborationStore.conversationHistory.push({ const roundEntry = {
roundNumber: message.roundNumber, roundNumber: message.roundNumber,
agentsMadeChanges: message.agentsMadeChanges, agentsMadeChanges: message.agentsMadeChanges,
timestamp: Date.now() timestamp: Date.now()
}) }
collaborationStore.conversationHistory.push(roundEntry)
if (collaborationStore.currentSession) {
const history = collaborationStore.currentSession.conversationHistory || []
collaborationStore.currentSession.conversationHistory = [...history, roundEntry]
}
collaborationStore.currentRound = message.roundNumber
currentWorkingAgent.value = null currentWorkingAgent.value = null
currentAgentThinking.value = '' currentAgentThinking.value = ''
@ -116,6 +130,9 @@ const handleWebSocketMessage = (message) => {
} else if (message.type === 'session_completed') { } else if (message.type === 'session_completed') {
currentWorkingAgent.value = null currentWorkingAgent.value = null
isAutoRunning.value = false isAutoRunning.value = false
if (collaborationStore.currentSession) {
collaborationStore.currentSession.status = 'completed'
}
} }
} }
@ -237,6 +254,10 @@ function formatAgentName(agent) {
<span class="hand"></span> <span class="hand"></span>
</div> </div>
<div class="agent-name-working">{{ formatAgentName(currentWorkingAgent) }}</div> <div class="agent-name-working">{{ formatAgentName(currentWorkingAgent) }}</div>
<div class="agent-thinking">
<span v-if="currentAgentThinking">{{ currentAgentThinking }}</span>
<span v-else>Analyzing the document...</span>
</div>
</div> </div>
<!-- Convergence Message --> <!-- Convergence Message -->
@ -461,6 +482,16 @@ function formatAgentName(agent) {
font-weight: 600; font-weight: 600;
} }
.agent-thinking {
flex: 1;
font-size: 0.95rem;
color: rgba(255, 255, 255, 0.75);
background: rgba(102, 126, 234, 0.08);
border-radius: 12px;
padding: 0.75rem 1rem;
line-height: 1.4;
}
.convergence-message { .convergence-message {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -1,7 +1,8 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed, onMounted, watch, ref, nextTick } from 'vue'
import { marked } from 'marked' import { marked } from 'marked'
import hljs from 'highlight.js' import hljs from 'highlight.js'
import mermaid from 'mermaid'
const props = defineProps({ const props = defineProps({
document: { document: {
@ -23,22 +24,28 @@ marked.setOptions({
const renderer = new marked.Renderer() const renderer = new marked.Renderer()
// Override code block rendering to add syntax highlighting // Configure mermaid once
renderer.code = ({ text, lang }) => { mermaid.initialize({
const language = lang || 'plain' startOnLoad: false,
let highlighted = text theme: 'dark',
securityLevel: 'loose'
})
if (hljs.getLanguage(language)) { // Override code block rendering to handle Mermaid and syntax highlighting
highlighted = hljs.highlight(text, { language }).value renderer.code = ({ text, lang }) => {
} else { if ((lang || '').toLowerCase() === 'mermaid') {
highlighted = hljs.highlight(text, { language: 'plain' }).value return `<div class="mermaid">${text}</div>`
} }
const language = lang && hljs.getLanguage(lang) ? lang : 'plaintext'
const highlighted = hljs.highlight(text, { language }).value
return `<pre><code class="hljs language-${language}">${highlighted}</code></pre>` return `<pre><code class="hljs language-${language}">${highlighted}</code></pre>`
} }
marked.setOptions({ renderer }) marked.setOptions({ renderer })
const containerRef = ref(null)
const renderedContent = computed(() => { const renderedContent = computed(() => {
if (!props.document) { if (!props.document) {
return '<p class="empty-state">No document content yet. Start the session to begin collaboration.</p>' return '<p class="empty-state">No document content yet. Start the session to begin collaboration.</p>'
@ -57,18 +64,40 @@ const renderedContent = computed(() => {
return `<pre><code>${escaped}</code></pre>` return `<pre><code>${escaped}</code></pre>`
} }
}) })
async function renderMermaid() {
await nextTick()
if (!containerRef.value) return
const nodes = containerRef.value.querySelectorAll('.mermaid')
if (!nodes.length) return
try {
await mermaid.run({ nodes })
} catch (error) {
console.error('Mermaid render error:', error)
}
}
onMounted(() => {
renderMermaid()
})
watch(() => [props.document, props.format], () => {
renderMermaid()
})
</script> </script>
<template> <template>
<div class="document-viewer"> <div class="document-viewer">
<div class="document-container" :class="`format-${format}`"> <div class="document-container" :class="`format-${format}`">
<div v-html="renderedContent" class="document-content"></div> <div ref="containerRef" v-html="renderedContent" class="document-content"></div>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
:import 'highlight.js/styles/atom-one-dark.css'; @import 'highlight.js/styles/atom-one-dark.css';
.document-viewer { .document-viewer {
width: 100%; width: 100%;

View File

@ -9,9 +9,36 @@ export const useCollaborationStore = defineStore('collaboration', () => {
const currentDocument = ref('') const currentDocument = ref('')
const currentRound = ref(0) const currentRound = ref(0)
const conversationHistory = ref([]) const conversationHistory = ref([])
const sessionsLoaded = ref(false)
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000' const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000'
/**
* Fetch recent collaborative sessions
*/
async function fetchSessions() {
if (loading.value) return
loading.value = true
error.value = null
try {
const response = await fetch(`${API_URL}/api/collaborate`)
if (!response.ok) {
throw new Error('Failed to fetch sessions')
}
const data = await response.json()
sessions.value = data.sessions || []
sessionsLoaded.value = true
return sessions.value
} catch (err) {
error.value = err.message
throw err
} finally {
loading.value = false
}
}
/** /**
* Create a new collaborative session * Create a new collaborative session
*/ */
@ -39,7 +66,8 @@ export const useCollaborationStore = defineStore('collaboration', () => {
const data = await response.json() const data = await response.json()
currentSession.value = data currentSession.value = data
sessions.value.unshift(data) sessions.value = [data, ...sessions.value.filter(s => s.sessionId !== data.sessionId)]
sessionsLoaded.value = true
return data return data
} catch (err) { } catch (err) {
@ -153,7 +181,24 @@ export const useCollaborationStore = defineStore('collaboration', () => {
throw new Error('Failed to complete session') throw new Error('Failed to complete session')
} }
return await response.json() const data = await response.json()
if (currentSession.value && currentSession.value.sessionId === sessionId) {
currentSession.value = {
...currentSession.value,
status: 'completed',
completedAt: new Date().toISOString()
}
}
sessions.value = sessions.value.map(session => {
if (session.sessionId === sessionId) {
return { ...session, status: 'completed', completedAt: new Date().toISOString() }
}
return session
})
return data
} catch (err) { } catch (err) {
error.value = err.message error.value = err.message
throw err throw err
@ -191,11 +236,13 @@ export const useCollaborationStore = defineStore('collaboration', () => {
return { return {
currentSession, currentSession,
sessions, sessions,
sessionsLoaded,
loading, loading,
error, error,
currentDocument, currentDocument,
currentRound, currentRound,
conversationHistory, conversationHistory,
fetchSessions,
createSession, createSession,
startSession, startSession,
runRound, runRound,