From de4600b4ce27b132ba6189e776a11b15ed41c150 Mon Sep 17 00:00:00 2001 From: Muyue Date: Sun, 19 Oct 2025 16:11:13 +0200 Subject: [PATCH] Fix critical bugs: document duplication, stop button, and session discovery Backend fixes: - Improve AI prompt with CRITICAL RULES emphasizing single section only - Add examples of CORRECT vs INCORRECT responses - Fix extractSection() to validate extracted content - Validate sections are < 5KB and start with markdown headers - Return error indicator for invalid section formats - This prevents IAs from returning entire document Frontend fixes: - Fix stop button: call ws.disconnect() to close WebSocket immediately - Stop processing WebSocket messages when isStopping is true - Clear messageInterval when stopping - Add archive sessions section on home page for easy access - Display all sessions in a searchable grid format - Add session status indicators and date - Scrollable grid for browsing all sessions - Integrated with existing search functionality User experience improvements: - Sessions now accessible both at top (Quick Access) and bottom (Archive) - Unified search works across all filtering - Stop button now truly stops the session - Better validation prevents document duplication bug --- backend/src/services/mistralClient.js | 50 ++++- .../src/components/CollaborativeInput.vue | 176 ++++++++++++++++++ .../src/components/CollaborativeSession.vue | 8 + 3 files changed, 224 insertions(+), 10 deletions(-) diff --git a/backend/src/services/mistralClient.js b/backend/src/services/mistralClient.js index 5d76744..a15005a 100644 --- a/backend/src/services/mistralClient.js +++ b/backend/src/services/mistralClient.js @@ -20,10 +20,11 @@ Your responsibilities: 3. Provide your thinking process and reasoning 4. Return ONLY the section (modified or new) with its header, or command to delete, or confirm it's good as-is -IMPORTANT RULES: +CRITICAL RULES - FOLLOW THESE EXACTLY: - Work on exactly ONE section only -- Never modify the entire document -- Return only the section you're working on, not the whole document +- NEVER return the entire document +- NEVER return multiple sections +- Return ONLY the section you're working on, not the whole document - You CAN create a new section if document is missing important content - You CAN delete a section if it's redundant, duplicate, or not useful - To delete a section, respond: "DELETE: ## Section Name" (with exact header) @@ -31,11 +32,21 @@ IMPORTANT RULES: - Think step-by-step about what could be improved or removed - Share your reasoning process -Format your response as: -THINKING: [Your analysis and reasoning] -DECISION: [What you'll modify, create, delete, or if keeping as-is] +RESPONSE FORMAT - FOLLOW THIS EXACTLY: +THINKING: [Your analysis and reasoning about the current document] +DECISION: [Exactly what you will do: modify section X, create new section Y, delete section Z, or keep as-is] SECTION: -[The modified section, new section, DELETE command, or confirmation that all is good]` +[ONLY ONE: Either a markdown section starting with # or ##, a DELETE command, or the text "Section is good, no changes needed"] + +EXAMPLE OF CORRECT RESPONSE: +THINKING: The Overview section is too brief and doesn't explain the main purpose. +DECISION: I will modify the Overview section to be more comprehensive. +SECTION: +## Overview +This is a technical document for designing system architecture... + +EXAMPLE OF INCORRECT RESPONSE (DO NOT DO THIS): +[The entire document repeated here] <- WRONG!` } /** @@ -102,13 +113,32 @@ export async function generateAgentResponseSync(agentName, prompt, currentDocume /** * Extract section from AI response + * Validates that we get a proper section, not the entire document */ export function extractSection(aiResponse) { - const sectionMatch = aiResponse.match(/SECTION:\s*([\s\S]*?)(?:$|THINKING:|DECISION:)/) + const sectionMatch = aiResponse.match(/SECTION:\s*([\s\S]*?)(?:$)/) if (sectionMatch) { - return sectionMatch[1].trim() + const extracted = sectionMatch[1].trim() + + // Validate: extracted should be either: + // 1. A markdown section starting with # (DELETE: or "Section is good...") + // 2. OR a single section with < 5000 chars (not entire document) + const isMarkdownSection = /^(#|DELETE:|Section is good)/.test(extracted) + const isShortEnough = extracted.length < 5000 // Single section should be < 5KB + + if (isMarkdownSection || isShortEnough) { + return extracted + } } - return aiResponse + + // Fallback: if no SECTION: tag found, treat entire response as section + // but only if it starts with markdown header or is a command + if (/^(#|DELETE:|Section is good)/.test(aiResponse)) { + return aiResponse.trim() + } + + // If none of the above, return error indicator + return "ERROR: Response does not contain a valid section format" } /** diff --git a/frontend/src/components/CollaborativeInput.vue b/frontend/src/components/CollaborativeInput.vue index 68478a3..54c2e18 100644 --- a/frontend/src/components/CollaborativeInput.vue +++ b/frontend/src/components/CollaborativeInput.vue @@ -327,6 +327,46 @@ const removeFile = () => { + +
+

📚 All Sessions Archive

+

Browse and access all previous sessions

+ + +
+ +
+ + +
+
+
+
+ #{{ session.sessionId }} + {{ session.status }} +
+

{{ session.prompt.substring(0, 120) }}...

+

{{ new Date(session.createdAt).toLocaleDateString() }}

+
+
+
+ +
+

No sessions found

+
+
+ @@ -968,6 +1008,142 @@ const removeFile = () => { z-index: 0; } +/* Sessions Archive Section */ +.sessions-archive-section { + margin: 3rem 0 2rem 0; + padding: 2rem; + background: rgba(102, 126, 234, 0.05); + border: 1px solid rgba(102, 126, 234, 0.2); + border-radius: 16px; + backdrop-filter: blur(10px); +} + +.sessions-archive-section h2 { + margin: 0 0 0.5rem 0; + font-size: 1.5rem; + color: rgba(255, 255, 255, 0.95); + font-weight: 700; +} + +.archive-subtitle { + margin: 0 0 1.5rem 0; + color: rgba(255, 255, 255, 0.6); + font-size: 0.95rem; +} + +.archive-filter { + margin-bottom: 1.5rem; +} + +.archive-search { + width: 100%; + padding: 0.75rem 1rem; + background: rgba(255, 255, 255, 0.07); + border: 1px solid rgba(102, 126, 234, 0.3); + color: rgba(255, 255, 255, 0.9); + border-radius: 8px; + font-size: 0.95rem; + transition: all 0.3s ease; +} + +.archive-search::placeholder { + color: rgba(255, 255, 255, 0.4); +} + +.archive-search:focus { + outline: none; + border-color: rgba(102, 126, 234, 0.6); + background: rgba(102, 126, 234, 0.1); +} + +.sessions-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 1rem; + max-height: 500px; + overflow-y: auto; +} + +.archive-session-item { + padding: 1rem; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(102, 126, 234, 0.2); + border-radius: 12px; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.archive-session-item:hover { + background: rgba(102, 126, 234, 0.15); + border-color: rgba(102, 126, 234, 0.4); + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(102, 126, 234, 0.15); +} + +.archive-session-item.status-completed { + border-color: rgba(76, 175, 80, 0.3); + background: rgba(76, 175, 80, 0.05); +} + +.archive-session-item.status-completed:hover { + background: rgba(76, 175, 80, 0.12); + border-color: rgba(76, 175, 80, 0.5); + box-shadow: 0 8px 20px rgba(76, 175, 80, 0.15); +} + +.archive-item-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.session-number { + color: rgba(102, 126, 234, 0.9); + font-weight: 700; + font-size: 0.95rem; +} + +.session-status-badge { + padding: 0.3rem 0.7rem; + background: rgba(102, 126, 234, 0.2); + color: rgba(102, 126, 234, 0.9); + border-radius: 20px; + font-size: 0.75rem; + font-weight: 600; + text-transform: capitalize; +} + +.archive-session-item.status-completed .session-status-badge { + background: rgba(76, 175, 80, 0.2); + color: rgba(76, 175, 80, 0.9); +} + +.archive-item-prompt { + margin: 0; + color: rgba(255, 255, 255, 0.8); + font-size: 0.9rem; + line-height: 1.4; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.archive-item-date { + margin: 0; + color: rgba(255, 255, 255, 0.4); + font-size: 0.8rem; +} + +.no-sessions { + text-align: center; + padding: 2rem 1rem; + color: rgba(255, 255, 255, 0.5); +} + @media (max-width: 768px) { .collaborative-input { padding: 1rem; diff --git a/frontend/src/components/CollaborativeSession.vue b/frontend/src/components/CollaborativeSession.vue index 67693d0..dfbf349 100644 --- a/frontend/src/components/CollaborativeSession.vue +++ b/frontend/src/components/CollaborativeSession.vue @@ -52,6 +52,11 @@ onMounted(async () => { ws.connect() const messageInterval = setInterval(() => { + // Stop processing messages if session is stopped + if (isStopping.value) { + clearInterval(messageInterval) + return + } if (ws.messages.value.length > 0) { const message = ws.messages.value.shift() handleWebSocketMessage(message) @@ -159,6 +164,9 @@ const stopSession = async () => { isStopping.value = true isAutoRunning.value = false if (autoRunTimeout.value) clearTimeout(autoRunTimeout.value) + // Close WebSocket connection immediately + ws.disconnect() + // Then complete the session on backend await completeSession() }