Skip to content

Commit ac3d2b1

Browse files
terraboopsclaude
andcommitted
fix: curate button shows loading state + error handling
The "Curate with AI" button was silently failing — no loading indicator during the ~4 minute LLM analysis, and errors were swallowed. Now shows spinner + status text during analysis, error messages on failure, and action count on success. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 630621f commit ac3d2b1

1 file changed

Lines changed: 57 additions & 29 deletions

File tree

trellis/web/frontend/templates/evolution.html

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ <h1 class="text-[1.15rem] font-medium text-[#3b3530] mb-0.5">Knowledge</h1>
1111
</div>
1212
</div>
1313
{% if agents %}
14-
<button onclick="curateAll()" id="curate-btn" class="btn btn-soft text-[0.78rem]">
15-
{{ icon('sparkles', 14) }} Curate with AI
16-
</button>
14+
<div class="flex items-center gap-3">
15+
<button onclick="curateAll()" id="curate-btn" class="btn btn-soft text-[0.78rem]">
16+
{{ icon('sparkles', 14) }} Curate with AI
17+
</button>
18+
<span id="curate-status" class="text-[0.75rem] text-[#8a8074] hidden"></span>
19+
</div>
1720
{% endif %}
1821
</div>
1922

@@ -163,8 +166,12 @@ <h2 class="text-[0.95rem] font-medium text-[#3b3530] mb-3">AI Curation Preview</
163166

164167
async function curateAll() {
165168
var btn = document.getElementById('curate-btn');
169+
var status = document.getElementById('curate-status');
166170
btn.disabled = true;
167-
btn.textContent = 'Curating...';
171+
btn.textContent = 'Analyzing...';
172+
status.classList.remove('hidden');
173+
status.textContent = 'This may take a few minutes — the AI is reviewing all knowledge entries.';
174+
status.className = 'text-[0.75rem] text-[#8a8074]';
168175

169176
try {
170177
var body = {};
@@ -176,32 +183,53 @@ <h2 class="text-[0.95rem] font-medium text-[#3b3530] mb-3">AI Curation Preview</
176183
body: JSON.stringify(body)
177184
});
178185

179-
if (resp.ok) {
180-
var data = await resp.json();
181-
var preview = document.getElementById('curation-preview');
182-
var content = document.getElementById('curation-content');
183-
184-
var html = '';
185-
for (var agent in data.actions) {
186-
var actions = data.actions[agent];
187-
var keeps = actions.filter(function(a) { return a.action === 'keep'; });
188-
var merges = actions.filter(function(a) { return a.action === 'merge'; });
189-
var drops = actions.filter(function(a) { return a.action === 'drop'; });
190-
191-
html += '<h3>' + agent + '</h3>';
192-
html += '<p>Keep: ' + keeps.length + ', Merge: ' + merges.length + ', Drop: ' + drops.length + '</p>';
193-
194-
drops.forEach(function(d) {
195-
html += '<div class="p-2 bg-[#f3e3e3] rounded-lg text-[0.8rem] mb-1">DROP [' + d.id + ']: ' + (d.reason || 'no reason') + '</div>';
196-
});
197-
merges.forEach(function(m) {
198-
html += '<div class="p-2 bg-[#eee8f6] rounded-lg text-[0.8rem] mb-1">MERGE ' + JSON.stringify(m.ids) + '</div>';
199-
});
200-
}
201-
202-
content.innerHTML = html || '<p>No changes proposed.</p>';
203-
preview.classList.remove('hidden');
186+
if (!resp.ok) {
187+
status.textContent = 'Error: ' + (resp.status === 500 ? 'Server error — check logs' : resp.statusText);
188+
status.className = 'text-[0.75rem] text-red-600';
189+
return;
204190
}
191+
192+
var data = await resp.json();
193+
var preview = document.getElementById('curation-preview');
194+
var content = document.getElementById('curation-content');
195+
content.textContent = '';
196+
197+
var totalActions = 0;
198+
for (var agent in data.actions) {
199+
var actions = data.actions[agent];
200+
totalActions += actions.length;
201+
var keeps = actions.filter(function(a) { return a.action === 'keep'; });
202+
var merges = actions.filter(function(a) { return a.action === 'merge'; });
203+
var drops = actions.filter(function(a) { return a.action === 'drop'; });
204+
205+
var heading = document.createElement('h3');
206+
heading.textContent = agent;
207+
content.appendChild(heading);
208+
209+
var summary = document.createElement('p');
210+
summary.textContent = 'Keep: ' + keeps.length + ', Merge: ' + merges.length + ', Drop: ' + drops.length;
211+
content.appendChild(summary);
212+
213+
drops.forEach(function(d) {
214+
var el = document.createElement('div');
215+
el.className = 'p-2 bg-[#f3e3e3] rounded-lg text-[0.8rem] mb-1';
216+
el.textContent = 'DROP [' + d.id + ']: ' + (d.reason || 'no reason');
217+
content.appendChild(el);
218+
});
219+
merges.forEach(function(m) {
220+
var el = document.createElement('div');
221+
el.className = 'p-2 bg-[#eee8f6] rounded-lg text-[0.8rem] mb-1';
222+
el.textContent = 'MERGE ' + JSON.stringify(m.ids);
223+
content.appendChild(el);
224+
});
225+
}
226+
227+
preview.classList.remove('hidden');
228+
status.textContent = totalActions + ' actions proposed.';
229+
status.className = 'text-[0.75rem] text-emerald-600';
230+
} catch (e) {
231+
status.textContent = 'Failed: ' + e.message;
232+
status.className = 'text-[0.75rem] text-red-600';
205233
} finally {
206234
btn.disabled = false;
207235
btn.textContent = 'Curate with AI';

0 commit comments

Comments
 (0)