diff --git a/webui/static/discover.js b/webui/static/discover.js
index 1872ea5d..bd378cc7 100644
--- a/webui/static/discover.js
+++ b/webui/static/discover.js
@@ -6431,14 +6431,22 @@ async function _openArtistMapExplorerWithName(name) {
}
function _showArtistMapSearchPrompt() {
+ // Search the metadata source and make the user PICK a real artist, rather
+ // than exploring whatever loose text they typed. Resolves with the chosen
+ // artist's resolved name (which the explorer hands to /artist-map/explore),
+ // or null if cancelled.
return new Promise(resolve => {
const existing = document.getElementById('artmap-search-prompt');
if (existing) existing.remove();
- const overlay = document.createElement('div');
+ let done = false;
+ let overlay;
+ const finish = (val) => { if (done) return; done = true; if (overlay) overlay.remove(); resolve(val); };
+
+ overlay = document.createElement('div');
overlay.id = 'artmap-search-prompt';
overlay.className = 'modal-overlay';
- overlay.onclick = (e) => { if (e.target === overlay) { overlay.remove(); resolve(null); } };
+ overlay.onclick = (e) => { if (e.target === overlay) finish(null); };
overlay.innerHTML = `
@@ -6449,32 +6457,79 @@ function _showArtistMapSearchPrompt() {
Artist Explorer
-
Enter an artist to explore their connections
+
Search and pick an artist to explore
-
+
+
-
-
+
`;
document.body.appendChild(overlay);
const input = overlay.querySelector('#artmap-explore-input');
- const goBtn = overlay.querySelector('#artmap-explore-go');
+ const results = overlay.querySelector('#artmap-explore-results');
+ const spinner = overlay.querySelector('#artmap-explore-spinner');
+ overlay.querySelector('#artmap-explore-cancel').onclick = () => finish(null);
- const submit = () => {
- const val = input.value.trim();
- overlay.remove();
- resolve(val || null);
+ const renderResults = (artists) => {
+ results.innerHTML = '';
+ if (!artists.length) {
+ results.innerHTML = 'No artists found
';
+ return;
+ }
+ artists.forEach(a => {
+ const img = a.image_url || '/static/placeholder-album.png';
+ const row = document.createElement('div');
+ row.className = 'artmap-explore-result';
+ row.innerHTML = `
+
+ ${escapeHtml(a.name)}
+ Explore →`;
+ row.onclick = () => finish(a.name); // pick the resolved artist, not raw text
+ results.appendChild(row);
+ });
};
- goBtn.onclick = submit;
- input.addEventListener('keydown', (e) => { if (e.key === 'Enter') submit(); });
+ let timer = null;
+ let token = 0;
+ const doSearch = () => {
+ const q = input.value.trim();
+ if (!q) { results.innerHTML = ''; spinner.style.display = 'none'; clearTimeout(timer); return; }
+ clearTimeout(timer);
+ timer = setTimeout(async () => {
+ const myToken = ++token;
+ spinner.style.display = 'flex';
+ try {
+ const resp = await fetch(`/api/discover/build-playlist/search-artists?query=${encodeURIComponent(q)}`);
+ const data = await resp.json();
+ if (myToken !== token) return; // a newer keystroke superseded this
+ renderResults((data && data.success && Array.isArray(data.artists)) ? data.artists : []);
+ } catch (e) {
+ if (myToken === token) results.innerHTML = 'Search failed — try again
';
+ } finally {
+ if (myToken === token) spinner.style.display = 'none';
+ }
+ }, 350);
+ };
+
+ input.addEventListener('input', doSearch);
+ input.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter') {
+ const first = results.querySelector('.artmap-explore-result');
+ if (first) first.click(); // Enter = pick top match, never raw text
+ } else if (e.key === 'Escape') {
+ finish(null);
+ }
+ });
setTimeout(() => input.focus(), 50);
});
}
diff --git a/webui/static/style.css b/webui/static/style.css
index f5b13454..35afbdef 100644
--- a/webui/static/style.css
+++ b/webui/static/style.css
@@ -34358,6 +34358,36 @@ div.artist-hero-badge {
.artmap-explore-input::placeholder { color: rgba(255,255,255,0.2); }
.artmap-search-prompt-actions { display: flex; justify-content: flex-end; gap: 8px; }
+/* Artist Explorer — search + select picker */
+.artmap-explore-search-wrap { position: relative; }
+.artmap-explore-search-wrap .artmap-explore-input { margin-bottom: 12px; }
+.artmap-explore-spinner { position: absolute; right: 12px; top: 11px; pointer-events: none; }
+.artmap-explore-spinner .watch-all-loading-spinner { width: 18px; height: 18px; border-width: 2px; }
+.artmap-explore-results {
+ max-height: 320px; overflow-y: auto; display: flex; flex-direction: column; gap: 4px;
+ margin-bottom: 18px;
+}
+.artmap-explore-results:empty { margin-bottom: 0; }
+.artmap-explore-result {
+ display: flex; align-items: center; gap: 12px; padding: 8px 10px; border-radius: 10px;
+ cursor: pointer; transition: background 0.15s ease;
+}
+.artmap-explore-result:hover { background: rgba(138,43,226,0.14); }
+.artmap-explore-result img {
+ width: 40px; height: 40px; border-radius: 50%; object-fit: cover; flex: 0 0 auto;
+ background: rgba(255,255,255,0.06);
+}
+.artmap-explore-result-name {
+ flex: 1 1 auto; min-width: 0; color: #fff; font-size: 14px; font-weight: 600;
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
+}
+.artmap-explore-result-go {
+ flex: 0 0 auto; font-size: 11px; font-weight: 700; color: rgba(138,43,226,0.9);
+ opacity: 0; transition: opacity 0.15s ease;
+}
+.artmap-explore-result:hover .artmap-explore-result-go { opacity: 1; }
+.artmap-explore-empty { padding: 18px; text-align: center; color: rgba(255,255,255,0.35); font-size: 13px; }
+
/* Genre picker modal */
.artmap-genre-picker-modal {
background: linear-gradient(165deg, #1e1e32 0%, #181828 100%);