feat: add custom query support for unresolved folder re-search

- Add SearchFolderRequest model for optional custom search query
- Update search endpoint to use custom query if provided
- Add search-again input field in UI for custom queries
- Increment search_attempts counter on re-search
This commit is contained in:
2026-06-06 23:31:25 +02:00
parent 486c5440f2
commit be7b210959
2 changed files with 95 additions and 12 deletions

View File

@@ -238,6 +238,63 @@
opacity: 0.7;
}
.search-again-row {
display: flex;
gap: 0.5rem;
margin-top: 0.5rem;
align-items: center;
}
.search-again-input {
flex: 1;
padding: 0.5rem 0.75rem;
border: 1px solid var(--color-border);
border-radius: var(--border-radius-md);
font-size: 0.85rem;
background: var(--color-surface);
color: var(--color-text);
}
.search-again-input:focus {
outline: none;
border-color: var(--color-accent);
}
.search-again-row .search-again-btn {
margin-top: 0;
}
.search-again-btn.searching {
pointer-events: none;
opacity: 0.7;
}
.search-again-row {
display: flex;
gap: 0.5rem;
margin-top: 0.5rem;
align-items: center;
}
.search-again-input {
flex: 1;
padding: 0.5rem 0.75rem;
border: 1px solid var(--color-border);
border-radius: var(--border-radius-md);
font-size: 0.85rem;
background: var(--color-surface);
color: var(--color-text);
}
.search-again-input:focus {
outline: none;
border-color: var(--color-accent);
}
.search-again-row .search-again-btn {
margin-top: 0;
}
/* Empty state */
.empty-state {
text-align: center;
@@ -471,12 +528,17 @@
return res.json();
}
async function reSearchFolder(folderName) {
async function reSearchFolder(folderName, customQuery) {
const token = localStorage.getItem('auth_token');
const encodedName = encodeURIComponent(folderName);
const body = customQuery ? JSON.stringify({ query: customQuery }) : '{}';
const res = await fetch(`/api/setup/unresolved/${encodedName}/search`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: body
});
return res.json();
}
@@ -497,15 +559,21 @@
? folder.search_suggestions.map(s => `
<div class="suggestion-item">
<i class="fas fa-link"></i>
<a href="${s.link}" class="suggestion-link" target="_blank">${s.title}</a>
<a href="${s.link}" class="suggestion-link" target="_blank">${s.name || s.title}</a>
</div>
`).join('')
: '<div class="no-suggestions"><i class="fas fa-info-circle"></i> No suggestions found</div>';
const searchAgainBtn = (folder.search_suggestions && folder.search_suggestions.length === 0)
? `<button class="search-again-btn" data-folder="${folder.folder_name}">
<i class="fas fa-search"></i> Search Again
</button>`
? `<div class="search-again-row">
<input type="text" class="search-again-input"
placeholder="Custom search..."
value="${folder.title || ''}"
data-folder="${folder.folder_name}">
<button class="search-again-btn" data-folder="${folder.folder_name}">
<i class="fas fa-search"></i> Search Again
</button>
</div>`
: '';
return `
@@ -650,25 +718,29 @@
btn.addEventListener('click', async (e) => {
const folder = e.target.dataset.folder || e.target.closest('button').dataset.folder;
const item = document.querySelector(`.folder-item[data-folder="${folder}"]`);
const searchInput = item.querySelector('.search-again-input');
const customQuery = searchInput ? searchInput.value.trim() : null;
btn.classList.add('searching');
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Searching...';
try {
const result = await reSearchFolder(folder);
const result = await reSearchFolder(folder, customQuery);
// Update suggestions in place
const suggestionsEl = item.querySelector('.suggestion-list');
if (result.search_suggestions && result.search_suggestions.length > 0) {
suggestionsEl.innerHTML = result.search_suggestions.map(s => `
<div class="suggestion-item">
<i class="fas fa-link"></i>
<a href="${s.link}" class="suggestion-link" target="_blank">${s.title}</a>
<a href="${s.link}" class="suggestion-link" target="_blank">${s.name || s.title}</a>
</div>
`).join('');
} else {
suggestionsEl.innerHTML = '<div class="no-suggestions"><i class="fas fa-info-circle"></i> No suggestions found</div>';
}
btn.remove();
// Remove the search row since we now have suggestions (or none)
const searchRow = item.querySelector('.search-again-row');
if (searchRow) searchRow.remove();
} catch (err) {
showToast('Search failed', 'error');
} finally {