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:
parent
588dd98e45
commit
de4600b4ce
@ -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"
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user