Skip to content

Commit eba8044

Browse files
committed
fix category dropdown
1 parent 128bb2f commit eba8044

File tree

3 files changed

+108
-135
lines changed

3 files changed

+108
-135
lines changed

app/main.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from fastapi.templating import Jinja2Templates
55
from pathlib import Path
66
from app.routes import install, actions
7+
from app.services.actions_loader import actions_loader
78
from api_analytics.fastapi import Analytics
89
import os
910
from dotenv import load_dotenv
@@ -34,9 +35,19 @@ async def favicon():
3435

3536
@app.get("/", response_class=HTMLResponse)
3637
async def index(request: Request):
38+
# Get all actions data for server-side rendering
39+
agents = [agent.dict() for agent in actions_loader.get_agents()]
40+
rules = [rule.dict() for rule in actions_loader.get_rules()]
41+
mcps = [mcp.dict() for mcp in actions_loader.get_mcps()]
42+
3743
return templates.TemplateResponse(
3844
"index.html",
39-
{"request": request}
45+
{
46+
"request": request,
47+
"agents": agents,
48+
"rules": rules,
49+
"mcps": mcps
50+
}
4051
)
4152

4253
@app.get("/health")

app/templates/components/quick_actions.html

Lines changed: 24 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,23 @@ <h2 class="text-xl font-black text-black mb-1">Quick start</h2>
1616
<span class="text-sm">🤖</span> Agents
1717
</div>
1818
<div id="agents-container" class="flex flex-wrap gap-2">
19-
<!-- Placeholder buttons to prevent layout shift -->
20-
<button class="action-button action-button--primary opacity-50 cursor-not-allowed" disabled>
21-
<div class="action-button__plus">+</div>
22-
<div class="action-button__content">
23-
<span class="action-button__label">[...]</span>
24-
</div>
25-
</button>
26-
<button class="action-button action-button--primary opacity-50 cursor-not-allowed" disabled>
19+
{% for agent in agents[:4] %}
20+
<button class="action-button action-button--primary" onclick="addAgent('{{ agent.slug or agent.name }}')">
2721
<div class="action-button__plus">+</div>
2822
<div class="action-button__content">
29-
<span class="action-button__label">[...]</span>
23+
<span class="action-button__label">{{ agent.display_name or agent.name }}</span>
3024
</div>
3125
</button>
26+
{% endfor %}
27+
{% if not agents %}
28+
<!-- Placeholder if no agents available -->
3229
<button class="action-button action-button--primary opacity-50 cursor-not-allowed" disabled>
3330
<div class="action-button__plus">+</div>
3431
<div class="action-button__content">
35-
<span class="action-button__label">[...]</span>
32+
<span class="action-button__label">No agents available</span>
3633
</div>
3734
</button>
35+
{% endif %}
3836
</div>
3937
</div>
4038

@@ -44,25 +42,23 @@ <h2 class="text-xl font-black text-black mb-1">Quick start</h2>
4442
<span class="text-sm">🔌</span> MCPs
4543
</div>
4644
<div id="mcps-container" class="flex flex-wrap gap-2">
47-
<!-- Placeholder buttons to prevent layout shift -->
48-
<button class="action-button action-button--secondary opacity-50 cursor-not-allowed" disabled>
45+
{% for mcp in mcps[:4] %}
46+
<button class="action-button action-button--secondary" onclick="addMCP('{{ mcp.name }}')">
4947
<div class="action-button__plus">+</div>
5048
<div class="action-button__content">
51-
<span class="action-button__label">[...]</span>
49+
<span class="action-button__label">{{ mcp.name }}</span>
5250
</div>
5351
</button>
52+
{% endfor %}
53+
{% if not mcps %}
54+
<!-- Placeholder if no MCPs available -->
5455
<button class="action-button action-button--secondary opacity-50 cursor-not-allowed" disabled>
5556
<div class="action-button__plus">+</div>
5657
<div class="action-button__content">
57-
<span class="action-button__label">[...]</span>
58-
</div>
59-
</button>
60-
<button class="action-button action-button--secondary opacity-50 cursor-not-allowed" disabled>
61-
<div class="action-button__plus">+</div>
62-
<div class="action-button__content">
63-
<span class="action-button__label">[...]</span>
58+
<span class="action-button__label">No MCPs available</span>
6459
</div>
6560
</button>
61+
{% endif %}
6662
</div>
6763
</div>
6864

@@ -72,25 +68,23 @@ <h2 class="text-xl font-black text-black mb-1">Quick start</h2>
7268
<span class="text-sm">📋</span> Rules
7369
</div>
7470
<div id="rules-container" class="flex flex-wrap gap-2">
75-
<!-- Placeholder buttons to prevent layout shift -->
76-
<button class="action-button action-button--accent opacity-50 cursor-not-allowed" disabled>
71+
{% for rule in rules[:4] %}
72+
<button class="action-button action-button--accent" onclick="addRule('{{ rule.slug or rule.name }}')">
7773
<div class="action-button__plus">+</div>
7874
<div class="action-button__content">
79-
<span class="action-button__label">[...]</span>
75+
<span class="action-button__label">{{ rule.display_name or rule.name }}</span>
8076
</div>
8177
</button>
78+
{% endfor %}
79+
{% if not rules %}
80+
<!-- Placeholder if no rules available -->
8281
<button class="action-button action-button--accent opacity-50 cursor-not-allowed" disabled>
8382
<div class="action-button__plus">+</div>
8483
<div class="action-button__content">
85-
<span class="action-button__label">[...]</span>
86-
</div>
87-
</button>
88-
<button class="action-button action-button--accent opacity-50 cursor-not-allowed" disabled>
89-
<div class="action-button__plus">+</div>
90-
<div class="action-button__content">
91-
<span class="action-button__label">[...]</span>
84+
<span class="action-button__label">No rules available</span>
9285
</div>
9386
</button>
87+
{% endif %}
9488
</div>
9589
</div>
9690
</div>
@@ -138,33 +132,8 @@ <h3 class="text-sm font-black text-black mb-2 flex items-center gap-1">
138132
workspaceSection.style.display = 'none';
139133
workspaceSection.style.transition = 'opacity 300ms ease-in-out';
140134
}
141-
142-
// Load actions from backend
143-
loadActionsFromBackend();
144135
});
145136

146-
// Fetch and display actions from backend API
147-
async function loadActionsFromBackend() {
148-
try {
149-
// Use cached data if available
150-
if (actionsCache) {
151-
renderActions(actionsCache);
152-
return;
153-
}
154-
155-
const response = await fetch('/api/actions/');
156-
if (!response.ok) throw new Error('Failed to fetch actions');
157-
158-
const data = await response.json();
159-
160-
// Cache the data
161-
actionsCache = data;
162-
163-
renderActions(data);
164-
} catch (error) {
165-
console.error('Error loading actions:', error);
166-
}
167-
}
168137

169138
// Render actions from data
170139
function renderActions(data) {

app/templates/components/workspace_actions.html

Lines changed: 72 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,15 @@ <h4 class="text-sm font-bold text-black mb-2">Actions</h4>
3434
</button>
3535
<div class="menu-dropdown" id="dropdown-mcps" style="display: none;">
3636
<div id="mcps-list" class="p-2">
37-
<div class="text-xs text-gray-500">Loading...</div>
37+
{% for mcp in mcps %}
38+
<div class="dropdown-item" onclick="toggleDropdownItem(this, '{{ mcp.name }}', 'mcps')">
39+
<span class="dropdown-item-icon">+</span>
40+
<span>{{ mcp.name }}</span>
41+
</div>
42+
{% endfor %}
43+
{% if not mcps %}
44+
<div class="text-xs text-gray-500">No MCPs available</div>
45+
{% endif %}
3846
</div>
3947
</div>
4048
</div>
@@ -52,7 +60,15 @@ <h4 class="text-sm font-bold text-black mb-2">Actions</h4>
5260
</button>
5361
<div class="menu-dropdown" id="dropdown-rules" style="display: none;">
5462
<div id="rules-list" class="p-2">
55-
<div class="text-xs text-gray-500">Loading...</div>
63+
{% for rule in rules %}
64+
<div class="dropdown-item" onclick="toggleDropdownItem(this, '{{ rule.slug or rule.name }}', 'rules')">
65+
<span class="dropdown-item-icon">+</span>
66+
<span>{{ rule.display_name or rule.name }}</span>
67+
</div>
68+
{% endfor %}
69+
{% if not rules %}
70+
<div class="text-xs text-gray-500">No rules available</div>
71+
{% endif %}
5672
</div>
5773
</div>
5874
</div>
@@ -70,7 +86,15 @@ <h4 class="text-sm font-bold text-black mb-2">Actions</h4>
7086
</button>
7187
<div class="menu-dropdown" id="dropdown-agents" style="display: none;">
7288
<div id="agents-list" class="p-2">
73-
<div class="text-xs text-gray-500">Loading...</div>
89+
{% for agent in agents %}
90+
<div class="dropdown-item" onclick="toggleDropdownItem(this, '{{ agent.slug or agent.name }}', 'agents')">
91+
<span class="dropdown-item-icon">+</span>
92+
<span>{{ agent.display_name or agent.name }}</span>
93+
</div>
94+
{% endfor %}
95+
{% if not agents %}
96+
<div class="text-xs text-gray-500">No agents available</div>
97+
{% endif %}
7498
</div>
7599
</div>
76100
</div>
@@ -301,29 +325,26 @@ <h4 class="text-sm font-bold text-black mb-2">Actions</h4>
301325
</style>
302326

303327
<script>
328+
// Initialize actions data from server-side rendering
329+
const serverActions = {
330+
agents: {{ agents | tojson | safe }},
331+
rules: {{ rules | tojson | safe }},
332+
mcps: {{ mcps | tojson | safe }}
333+
};
334+
304335
// Track loaded data
305336
let loadedData = {};
306337

307-
// Search functionality
308-
let allActions = [];
338+
// Search functionality - initialize with server data
339+
let allActions = [
340+
...(serverActions.mcps || []).map(item => ({...item, type: 'MCP'})),
341+
...(serverActions.rules || []).map(item => ({...item, type: 'Rule'})),
342+
...(serverActions.agents || []).map(item => ({...item, type: 'Agent'}))
343+
];
309344
let searchTimeout = null;
310345

311-
// Initialize search data
312-
async function initSearchData() {
313-
try {
314-
const response = await fetch('/api/actions/');
315-
if (response.ok) {
316-
const data = await response.json();
317-
allActions = [
318-
...(data.mcps || []).map(item => ({...item, type: 'MCP'})),
319-
...(data.rules || []).map(item => ({...item, type: 'Rule'})),
320-
...(data.agents || []).map(item => ({...item, type: 'Agent'}))
321-
];
322-
}
323-
} catch (error) {
324-
console.error('Error loading search data:', error);
325-
}
326-
}
346+
// Initialize search data - no longer needed as data comes from server
347+
// The allActions array is already initialized with server data above
327348

328349
// Search actions
329350
function searchActions(query) {
@@ -437,7 +458,7 @@ <h4 class="text-sm font-bold text-black mb-2">Actions</h4>
437458

438459
// Initialize on load
439460
document.addEventListener('DOMContentLoaded', function() {
440-
initSearchData();
461+
// No need to call initSearchData - data is already loaded from server
441462
// Wait a bit for workspace manager to initialize
442463
setTimeout(() => {
443464
restoreActionStates();
@@ -481,19 +502,9 @@ <h4 class="text-sm font-bold text-black mb-2">Actions</h4>
481502

482503
// Insert action into context
483504
async function insertIntoContext(type) {
484-
if (type === 'agents') {
485-
// Default agent insertion
486-
const defaultAgent = 'code-searcher';
487-
await installAgent(defaultAgent);
488-
} else if (type === 'mcps') {
489-
// Default MCP insertion
490-
const defaultMcp = 'filesystem';
491-
await installMCP(defaultMcp);
492-
} else if (type === 'rules') {
493-
// Default rule insertion
494-
const defaultRule = 'code-quality';
495-
await installRule(defaultRule);
496-
}
505+
// No longer auto-install default items
506+
// Users should explicitly choose which items they want
507+
// This function is kept for potential future use
497508
}
498509

499510
// Remove action from context
@@ -527,51 +538,33 @@ <h4 class="text-sm font-bold text-black mb-2">Actions</h4>
527538
}
528539
}
529540

530-
// Load dropdown data
531-
async function loadDropdownData(type) {
532-
try {
533-
const response = await fetch('/api/actions/');
534-
if (!response.ok) throw new Error('Failed to fetch actions');
535-
536-
const data = await response.json();
537-
let container, items;
538-
539-
switch(type) {
540-
case 'mcps':
541-
container = document.getElementById('mcps-list');
542-
items = data.mcps || [];
543-
break;
544-
case 'rules':
545-
container = document.getElementById('rules-list');
546-
items = data.rules || [];
547-
break;
548-
case 'agents':
549-
container = document.getElementById('agents-list');
550-
items = data.agents || [];
551-
break;
552-
}
553-
554-
if (container && items.length > 0) {
555-
const state = window.workspaceManager?.getState();
556-
container.innerHTML = items.map(item => {
557-
// Use slug for internal ID, display_name for display
558-
const itemId = item.slug || item.name;
559-
const displayName = item.display_name || item.name;
560-
const isChecked = state ? state.isActionItemChecked(type, itemId) : false;
561-
return `
562-
<div
563-
class="dropdown-item ${isChecked ? 'active' : ''}"
564-
onclick="toggleDropdownItem(this, '${itemId}', '${type}')"
565-
>
566-
<span class="dropdown-item-icon">${isChecked ? '−' : '+'}</span>
567-
<span>${displayName}</span>
568-
</div>
569-
`;
570-
}).join('');
541+
// Load dropdown data - now just updates the UI state based on what's already rendered
542+
function loadDropdownData(type) {
543+
// The dropdown items are now rendered server-side with Jinja
544+
// This function just needs to update the UI state (active/inactive)
545+
const state = window.workspaceManager?.getState();
546+
if (!state) return;
547+
548+
const container = document.getElementById(`${type}-list`);
549+
if (!container) return;
550+
551+
// Update each dropdown item's state
552+
const items = container.querySelectorAll('.dropdown-item');
553+
items.forEach(item => {
554+
const itemName = item.getAttribute('data-item-id') ||
555+
item.onclick.toString().match(/toggleDropdownItem\(this,\s*'([^']+)'/)?.[1];
556+
if (itemName) {
557+
const isChecked = state.isActionItemChecked(type, itemName);
558+
const icon = item.querySelector('.dropdown-item-icon');
559+
if (isChecked) {
560+
item.classList.add('active');
561+
if (icon) icon.textContent = '−';
562+
} else {
563+
item.classList.remove('active');
564+
if (icon) icon.textContent = '+';
565+
}
571566
}
572-
} catch (error) {
573-
console.error(`Error loading ${type}:`, error);
574-
}
567+
});
575568
}
576569

577570
// Toggle individual dropdown item

0 commit comments

Comments
 (0)