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
This commit is contained in:
Augustin ROUX 2025-10-19 16:11:13 +02:00
parent 588dd98e45
commit de4600b4ce
3 changed files with 224 additions and 10 deletions

View File

@ -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"
}
/**

View File

@ -327,6 +327,46 @@ const removeFile = () => {
</button>
</form>
<!-- All Sessions Archive -->
<div class="sessions-archive-section">
<h2>📚 All Sessions Archive</h2>
<p class="archive-subtitle">Browse and access all previous sessions</p>
<!-- Archive Filter -->
<div class="archive-filter">
<input
v-model="searchQuery"
type="text"
placeholder="Search sessions..."
class="archive-search"
/>
</div>
<!-- Archive Sessions -->
<div v-if="filteredSessions.length > 0" class="archive-sessions">
<div class="sessions-list">
<div
v-for="session in filteredSessions"
:key="session.sessionId"
@click="handleOpenSession(session.sessionId)"
class="archive-session-item"
:class="'status-' + session.status"
>
<div class="archive-item-header">
<span class="session-number">#{{ session.sessionId }}</span>
<span class="session-status-badge">{{ session.status }}</span>
</div>
<p class="archive-item-prompt">{{ session.prompt.substring(0, 120) }}...</p>
<p class="archive-item-date">{{ new Date(session.createdAt).toLocaleDateString() }}</p>
</div>
</div>
</div>
<div v-else class="no-sessions">
<p>No sessions found</p>
</div>
</div>
<footer class="footer">
<p>Typical session: 2-5 rounds for complete consensus. Output format: Markdown</p>
</footer>
@ -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;

View File

@ -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()
}