feat: enhance file selection and prompt generation

- Add gitignore filtering to file tree and search
- Introduce recursive directory listing API
- Enable recursive directory selection in UI
- Pass selected paths directly for prompt generation
- Refactor API endpoint names and error handling
This commit is contained in:
Mahmoud-Emad
2025-09-09 16:31:08 +03:00
parent 154c08411c
commit cc837a1427
5 changed files with 658 additions and 189 deletions

View File

@@ -141,14 +141,27 @@ class SimpleFileTree {
checkbox.type = 'checkbox';
checkbox.className = 'tree-checkbox';
checkbox.checked = selected.has(path);
checkbox.addEventListener('change', (e) => {
checkbox.addEventListener('change', async (e) => {
e.stopPropagation();
if (checkbox.checked) {
selected.add(path);
// If this is a directory, also select all its children
if (item.type === 'directory') {
await this.selectDirectoryChildren(path, true);
} else {
// For files, update UI immediately since no async operation
this.updateSelectionUI();
}
} else {
selected.delete(path);
// If this is a directory, also deselect all its children
if (item.type === 'directory') {
await this.selectDirectoryChildren(path, false);
} else {
// For files, update UI immediately since no async operation
this.updateSelectionUI();
}
}
this.updateSelectionUI();
});
// Expand/collapse button for directories
@@ -218,11 +231,20 @@ class SimpleFileTree {
// Remove from loaded paths so it can be reloaded when expanded again
this.loadedPaths.delete(dirPath);
} else {
// Expand
// Expand - update UI optimistically but revert on error
this.expandedDirs.add(dirPath);
if (expandBtn) expandBtn.innerHTML = '▼';
if (icon) icon.textContent = '📂';
await this.loadChildren(dirPath);
// Try to load children
const success = await this.loadChildren(dirPath);
// If loading failed, revert the UI state
if (!success) {
this.expandedDirs.delete(dirPath);
if (expandBtn) expandBtn.innerHTML = '▶';
if (icon) icon.textContent = '📁';
}
}
}
@@ -256,7 +278,7 @@ class SimpleFileTree {
if (r.error) {
console.warn('Failed to load directory:', parentPath, r.error);
return;
return false; // Return false to indicate failure
}
// Sort items: directories first, then files
@@ -271,7 +293,7 @@ class SimpleFileTree {
const parentElement = qs(`[data-path="${parentPath}"]`);
if (!parentElement) {
console.warn('Parent element not found for path:', parentPath);
return;
return false; // Return false to indicate failure
}
const parentDepth = parseInt(parentElement.dataset.depth || '0');
@@ -316,6 +338,7 @@ class SimpleFileTree {
});
this.loadedPaths.add(parentPath);
return true; // Return true to indicate success
}
getDepth(path) {
@@ -378,6 +401,58 @@ class SimpleFileTree {
if (tokenCountEl) tokenCountEl.textContent = tokens.toString();
}
// Select or deselect all children of a directory recursively
async selectDirectoryChildren(dirPath, select) {
// First, get all children from API to update the selection state
await this.selectDirectoryChildrenFromAPI(dirPath, select);
// Then, update any currently visible children in the DOM
this.updateVisibleCheckboxes();
// Update the selection UI once at the end
this.updateSelectionUI();
}
// Update all visible checkboxes to match the current selection state
updateVisibleCheckboxes() {
const treeItems = document.querySelectorAll('.tree-item');
treeItems.forEach(item => {
const itemPath = item.dataset.path;
const checkbox = item.querySelector('.tree-checkbox');
if (checkbox && itemPath) {
// Set checkbox state based on current selection
checkbox.checked = selected.has(itemPath);
}
});
}
// Select directory children using API to get complete recursive list
async selectDirectoryChildrenFromAPI(dirPath, select) {
try {
const response = await fetch(`/api/heroprompt/workspaces/${encodeURIComponent(currentWs)}/list?path=${encodeURIComponent(dirPath)}`);
if (response.ok) {
const data = await response.json();
if (data.children) {
data.children.forEach(child => {
const childPath = child.path;
if (select) {
selected.add(childPath);
} else {
selected.delete(childPath);
}
});
}
} else {
console.error('Failed to fetch directory children:', response.status, response.statusText);
const errorText = await response.text();
console.error('Error response:', errorText);
}
} catch (error) {
console.error('Error selecting directory children:', error);
}
}
createFileCard(path) {
const card = document.createElement('div');
card.className = 'file-card';
@@ -1026,19 +1101,15 @@ async function generatePrompt() {
outputEl.innerHTML = '<div class="loading">Generating prompt...</div>';
try {
// sync selection to backend before generating
// Pass selections directly to prompt generation
const paths = Array.from(selected);
const syncResult = await post(`/api/heroprompt/workspaces/${encodeURIComponent(currentWs)}/selection`, {
paths: JSON.stringify(paths)
});
if (syncResult.error) {
throw new Error(`Failed to sync selection: ${syncResult.error}`);
}
const formData = new URLSearchParams();
formData.append('text', promptText);
formData.append('selected_paths', JSON.stringify(paths));
const r = await fetch(`/api/heroprompt/workspaces/${encodeURIComponent(currentWs)}/prompt`, {
method: 'POST',
body: new URLSearchParams({ text: promptText })
body: formData
});
if (!r.ok) {