var return_to_top = document.getElementById('return-to-top');
var lidarr_get_artists_button = document.getElementById(
'lidarr-get-artists-button'
);
var start_stop_button = document.getElementById('start-stop-button');
var lidarr_status = document.getElementById('lidarr-status');
var lidarr_spinner = document.getElementById('lidarr-spinner');
var load_more_button = document.getElementById('load-more-btn');
var header_spinner = document.getElementById('artists-loading-spinner');
var lidarr_item_list = document.getElementById('lidarr-item-list');
var lidarr_select_all_checkbox = document.getElementById('lidarr-select-all');
var lidarr_select_all_container = document.getElementById(
'lidarr-select-all-container'
);
var config_modal = document.getElementById('config-modal');
var lidarr_sidebar = document.getElementById('lidarr-sidebar');
const START_LABEL = 'Start discovery';
const STOP_LABEL = 'Stop';
var save_message = document.getElementById('save-message');
var save_changes_button = document.getElementById('save-changes-button');
var settings_form = document.getElementById('settings-form');
const lidarr_address = document.getElementById('lidarr-address');
const lidarr_api_key = document.getElementById('lidarr-api-key');
const root_folder_path = document.getElementById('root-folder-path');
const youtube_api_key = document.getElementById('youtube-api-key');
const openai_api_key_input = document.getElementById('openai-api-key');
const openai_model_input = document.getElementById('openai-model');
const openai_max_seed_artists_input = document.getElementById(
'openai-max-seed-artists'
);
const similar_artist_batch_size_input = document.getElementById(
'similar-artist-batch-size'
);
const quality_profile_id_input = document.getElementById('quality-profile-id');
const metadata_profile_id_input = document.getElementById(
'metadata-profile-id'
);
const lidarr_api_timeout_input = document.getElementById('lidarr-api-timeout');
const fallback_to_top_result_checkbox = document.getElementById(
'fallback-to-top-result'
);
const search_for_missing_albums_checkbox = document.getElementById(
'search-for-missing-albums'
);
const dry_run_adding_to_lidarr_checkbox = document.getElementById(
'dry-run-adding-to-lidarr'
);
const auto_start_checkbox = document.getElementById('auto-start');
const auto_start_delay_input = document.getElementById('auto-start-delay');
const last_fm_api_key_input = document.getElementById('last-fm-api-key');
const last_fm_api_secret_input = document.getElementById('last-fm-api-secret');
const api_key_input = document.getElementById('api-key');
const personalLastfmButton = document.getElementById('personal-lastfm-button');
const personalLastfmSpinner = document.getElementById(
'personal-lastfm-spinner'
);
const personalLastfmHint = document.getElementById('personal-lastfm-hint');
const ai_assist_button = document.getElementById('ai-assist-button');
const ai_helper_modal = document.getElementById('ai-helper-modal');
const ai_helper_form = document.getElementById('ai-helper-form');
const ai_helper_input = document.getElementById('ai-helper-input');
const ai_helper_error = document.getElementById('ai-helper-error');
const ai_helper_results = document.getElementById('ai-helper-results');
const ai_helper_submit = document.getElementById('ai-helper-submit');
const ai_helper_spinner = document.getElementById('ai-helper-spinner');
var lidarr_items = [];
var is_admin = false;
var socket = io({
withCredentials: true,
});
var personalSourcesState = null;
var personalDiscoveryState = {
inFlight: false,
source: null,
};
// Initial load flow control
let initialLoadComplete = false;
let initialLoadHasMore = false;
let loadMorePending = false;
if (ai_helper_modal) {
ai_helper_modal.addEventListener('hidden.bs.modal', function () {
if (ai_helper_input) {
ai_helper_input.value = '';
}
reset_ai_feedback();
set_ai_form_loading(false);
if (ai_helper_submit) {
ai_helper_submit.blur();
}
});
}
if (ai_helper_form) {
ai_helper_form.addEventListener('submit', function (event) {
event.preventDefault();
if (!socket.connected) {
show_toast('Connection Lost', 'Please reconnect to continue.');
return;
}
if (!ai_helper_input) {
return;
}
var prompt = ai_helper_input.value.trim();
if (!prompt) {
if (ai_helper_error) {
ai_helper_error.textContent =
'Tell us what to search for before asking the AI assistant.';
ai_helper_error.classList.remove('d-none');
}
return;
}
reset_ai_feedback();
set_ai_form_loading(true);
begin_ai_discovery_flow();
socket.emit('ai_prompt_req', {
prompt: prompt,
});
});
}
if (personalLastfmButton) {
personalLastfmButton.addEventListener('click', function () {
startPersonalDiscovery('lastfm');
});
}
updatePersonalButtons();
socket.on('connect', function () {
socket.emit('personal_sources_poll');
});
socket.on('user_info', function (data) {
is_admin = data.is_admin || false;
});
function show_header_spinner() {
if (header_spinner) {
header_spinner.classList.remove('d-none');
}
}
function hide_header_spinner() {
if (header_spinner) {
header_spinner.classList.add('d-none');
}
}
function escape_html(text) {
if (text === null || text === undefined) {
return '';
}
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function render_biography_html(biography) {
if (typeof biography !== 'string') {
return '';
}
var trimmed = biography.trim();
if (!trimmed) {
return '';
}
var containsHtml = /<\/?[a-z][\s\S]*>/i.test(trimmed);
if (containsHtml) {
var sanitizedHtml;
if (typeof DOMPurify !== 'undefined') {
sanitizedHtml = DOMPurify.sanitize(trimmed, {
USE_PROFILES: { html: true },
});
} else {
sanitizedHtml = escape_html(trimmed);
}
if (
sanitizedHtml &&
!/
]/i.test(sanitizedHtml) &&
/\n/.test(sanitizedHtml)
) {
var htmlBlocks = sanitizedHtml
.split(/\n{2,}/)
.map(function (block) {
return block.trim();
})
.filter(function (block) {
return block.length > 0;
})
.map(function (block) {
return '
' + block.replace(/\n/g, '
') + '
';
})
.join('');
if (htmlBlocks) {
return htmlBlocks;
}
}
return sanitizedHtml;
}
var paragraphs = trimmed
.split(/\n{2,}/)
.map(function (block) {
return block.trim();
})
.filter(function (block) {
return block.length > 0;
})
.map(function (block) {
return '' + escape_html(block).replace(/\n/g, '
') + '
';
})
.join('');
if (!paragraphs) {
return escape_html(trimmed);
}
if (typeof DOMPurify !== 'undefined') {
return DOMPurify.sanitize(paragraphs, {
USE_PROFILES: { html: true },
});
}
return paragraphs;
}
function render_loading_spinner(message) {
return `
`;
}
function reset_ai_feedback() {
if (ai_helper_error) {
ai_helper_error.textContent = '';
ai_helper_error.classList.add('d-none');
}
if (ai_helper_results) {
ai_helper_results.innerHTML = '';
ai_helper_results.classList.add('d-none');
}
}
function set_ai_form_loading(isLoading) {
if (ai_helper_submit) {
ai_helper_submit.disabled = isLoading;
}
if (ai_helper_spinner) {
if (isLoading) {
ai_helper_spinner.classList.remove('d-none');
} else {
ai_helper_spinner.classList.add('d-none');
}
}
}
function begin_ai_discovery_flow() {
clear_all();
show_header_spinner();
}
function set_hint_text(element, message) {
if (!element) {
return;
}
var hasMessage = !!(message && message.trim());
element.textContent = hasMessage ? message : '';
if (hasMessage) {
element.classList.remove('d-none');
} else {
element.classList.add('d-none');
}
}
function updatePersonalButtons() {
var state = personalSourcesState || {};
var lastfm = state.lastfm || null;
if (personalLastfmButton) {
var lastfmReady = !!(lastfm && lastfm.enabled);
var loading = personalDiscoveryState.inFlight;
var lastfmTitle = 'Stream recommendations from your Last.fm profile.';
if (!lastfm) {
personalLastfmButton.disabled = true;
lastfmTitle = 'Loading Last.fm availability…';
set_hint_text(personalLastfmHint, '');
} else {
personalLastfmButton.disabled = !lastfmReady || loading;
if (lastfmReady) {
var readyMessage = '';
if (lastfm.username) {
readyMessage =
'Ready with Last.fm profile ' + lastfm.username + '.';
} else {
readyMessage =
'Ready to use your Last.fm listening history.';
}
set_hint_text(personalLastfmHint, readyMessage);
} else {
set_hint_text(
personalLastfmHint,
lastfm.reason || 'Last.fm configuration is incomplete.'
);
lastfmTitle = lastfm.reason || lastfmTitle;
}
}
personalLastfmButton.title = lastfmTitle;
}
}
function setPersonalDiscoveryLoading(source, isLoading) {
var targetSource = source;
if (!isLoading) {
targetSource = personalDiscoveryState.source;
}
personalDiscoveryState.inFlight = !!isLoading;
personalDiscoveryState.source = isLoading ? source : null;
if (personalLastfmSpinner) {
personalLastfmSpinner.classList.toggle(
'd-none',
!(personalDiscoveryState.inFlight && targetSource === 'lastfm')
);
}
if (isLoading) {
if (source === 'lastfm' && personalLastfmButton) {
personalLastfmButton.blur();
}
}
updatePersonalButtons();
}
function startPersonalDiscovery(source) {
if (!socket.connected) {
show_toast('Connection Lost', 'Please reconnect to continue.');
return;
}
if (!personalSourcesState) {
show_toast(
'Personal discovery',
'Hang tight while we load your personal listening services.'
);
socket.emit('personal_sources_poll');
return;
}
var sourceState = personalSourcesState[source];
if (!sourceState || !sourceState.enabled) {
var reason = sourceState && sourceState.reason;
var serviceTitle =
source === 'lastfm' ? 'Last.fm discovery' : 'Personal discovery';
show_toast(
serviceTitle,
reason ||
'Configure this service in your profile to unlock personal picks.'
);
return;
}
begin_ai_discovery_flow();
setPersonalDiscoveryLoading(source, true);
socket.emit('user_recs_req', { source: source });
}
function show_modal_with_lock(modalId, onHidden) {
var modalEl = document.getElementById(modalId);
if (!modalEl) {
return null;
}
var scrollbarWidth =
window.innerWidth - document.documentElement.clientWidth;
document.body.style.overflow = 'hidden';
document.body.style.paddingRight = `${scrollbarWidth}px`;
var modalInstance = bootstrap.Modal.getOrCreateInstance(modalEl);
var hiddenHandler = function () {
document.body.style.overflow = 'auto';
document.body.style.paddingRight = '0';
modalEl.removeEventListener('hidden.bs.modal', hiddenHandler);
if (typeof onHidden === 'function') {
onHidden();
}
};
modalEl.addEventListener('hidden.bs.modal', hiddenHandler, { once: true });
modalInstance.show();
return modalInstance;
}
function ensure_audio_modal_visible() {
var modalEl = document.getElementById('audio-player-modal');
if (!modalEl) {
return;
}
if (!modalEl.classList.contains('show')) {
show_modal_with_lock('audio-player-modal', function () {
var container = document.getElementById('audio-player-modal-body');
if (container) {
container.innerHTML = '';
}
});
}
}
function show_audio_modal_loading(artistName) {
var bodyEl = document.getElementById('audio-player-modal-body');
var titleEl = document.getElementById('audio-player-modal-label');
if (titleEl) {
titleEl.textContent = `Fetching sample for ${artistName}`;
}
if (bodyEl) {
bodyEl.innerHTML = render_loading_spinner('Loading sample...');
}
ensure_audio_modal_visible();
}
function update_audio_modal_content(payload) {
var bodyEl = document.getElementById('audio-player-modal-body');
var titleEl = document.getElementById('audio-player-modal-label');
var artistName = payload && payload.artist ? payload.artist : '';
var trackName = payload && payload.track ? payload.track : '';
if (titleEl) {
if (artistName && trackName) {
titleEl.textContent = `${artistName} – ${trackName}`;
} else {
titleEl.textContent = artistName || trackName || 'Preview Player';
}
}
if (!bodyEl) {
return;
}
if (payload && payload.videoId) {
var safeVideoId = encodeURIComponent(payload.videoId);
var safeTitle = escape_html(
`${artistName || 'Unknown artist'} – ${
trackName || 'Unknown track'
}`
);
bodyEl.innerHTML = `
`;
} else if (payload && payload.previewUrl) {
var safePreviewUrl = encodeURI(payload.previewUrl);
var sourceLabel =
payload.source === 'itunes'
? 'Preview via Apple Music'
: 'Audio preview';
bodyEl.innerHTML = `
${escape_html(
sourceLabel
)}
`;
} else {
bodyEl.innerHTML = `${escape_html(
'Sample unavailable'
)}
`;
}
ensure_audio_modal_visible();
}
function show_audio_modal_error(message) {
var bodyEl = document.getElementById('audio-player-modal-body');
var titleEl = document.getElementById('audio-player-modal-label');
if (titleEl) {
titleEl.textContent = 'Sample unavailable';
}
if (bodyEl) {
var safeMessage = escape_html(message);
bodyEl.innerHTML = `${safeMessage}
`;
}
ensure_audio_modal_visible();
}
function show_bio_modal_loading(artistName) {
var titleEl = document.getElementById('bio-modal-title');
var bodyEl = document.getElementById('modal-body');
if (titleEl) {
titleEl.textContent = artistName;
}
if (bodyEl) {
bodyEl.innerHTML = render_loading_spinner('Loading biography...');
}
show_modal_with_lock('bio-modal-modal');
}
function check_if_all_selected() {
var checkboxes = document.querySelectorAll('input[name="lidarr-item"]');
var all_checked = true;
for (var i = 0; i < checkboxes.length; i++) {
if (!checkboxes[i].checked) {
all_checked = false;
break;
}
}
lidarr_select_all_checkbox.checked = all_checked;
}
function load_lidarr_data(response) {
var every_check_box = document.querySelectorAll(
'input[name="lidarr-item"]'
);
if (response.Running) {
start_stop_button.classList.remove('btn-success');
start_stop_button.classList.add('btn-warning');
start_stop_button.textContent = STOP_LABEL;
every_check_box.forEach((item) => {
item.disabled = true;
});
lidarr_select_all_checkbox.disabled = true;
lidarr_get_artists_button.disabled = true;
} else {
start_stop_button.classList.add('btn-success');
start_stop_button.classList.remove('btn-warning');
start_stop_button.textContent = START_LABEL;
every_check_box.forEach((item) => {
item.disabled = false;
});
lidarr_select_all_checkbox.disabled = false;
lidarr_get_artists_button.disabled = false;
}
check_if_all_selected();
}
function create_load_more_button() {
if (!load_more_button) return;
if (!initialLoadComplete || !initialLoadHasMore) {
load_more_button.classList.add('d-none');
load_more_button.disabled = false;
return;
}
load_more_button.classList.remove('d-none');
load_more_button.disabled = loadMorePending;
}
function remove_load_more_button() {
if (!load_more_button) return;
load_more_button.classList.add('d-none');
load_more_button.disabled = false;
}
function append_artists(artists) {
var artist_row = document.getElementById('artist-row');
var template = document.getElementById('artist-template');
if (!initialLoadComplete) {
remove_load_more_button();
}
artists.forEach(function (artist) {
var clone = document.importNode(template.content, true);
var artist_col = clone.querySelector('#artist-column');
var cardEl = artist_col.querySelector('.artist-card');
var statusDot = cardEl ? cardEl.querySelector('.led') : null;
var imageContainer = artist_col.querySelector('.artist-img-container');
var coverImage = imageContainer
? imageContainer.querySelector('.card-img-top')
: null;
artist_col.querySelector('.card-title').textContent = artist.Name;
var similarityEl = artist_col.querySelector('.similarity');
if (similarityEl) {
const hasScore =
typeof artist.SimilarityScore === 'number' &&
!Number.isNaN(artist.SimilarityScore);
if (
hasScore ||
(typeof artist.Similarity === 'string' &&
artist.Similarity.trim().length > 0)
) {
const label =
typeof artist.Similarity === 'string' &&
artist.Similarity.trim().length > 0
? artist.Similarity
: `Similarity: ${(artist.SimilarityScore * 100).toFixed(
1
)}%`;
similarityEl.textContent = label;
similarityEl.classList.remove('d-none');
} else {
similarityEl.textContent = '';
similarityEl.classList.add('d-none');
}
}
artist_col.querySelector('.genre').textContent = artist.Genre;
if (imageContainer) {
imageContainer.classList.remove('artist-placeholder');
var existingPlaceholder = imageContainer.querySelector(
'.artist-placeholder-letter'
);
if (existingPlaceholder) {
existingPlaceholder.remove();
}
}
if (artist.Img_Link && coverImage) {
coverImage.src = artist.Img_Link;
coverImage.alt = artist.Name;
coverImage.classList.remove('d-none');
} else if (imageContainer) {
if (coverImage) {
coverImage.remove();
}
imageContainer.classList.add('artist-placeholder');
var placeholderSpan = document.createElement('span');
placeholderSpan.className = 'artist-placeholder-letter';
var firstLetter =
typeof artist.Name === 'string' && artist.Name.length > 0
? artist.Name.charAt(0).toUpperCase()
: '?';
placeholderSpan.textContent = firstLetter;
imageContainer.appendChild(placeholderSpan);
}
var add_button = artist_col.querySelector('.add-to-lidarr-btn');
add_button.dataset.defaultText =
add_button.dataset.defaultText || add_button.textContent;
// Set button text and handler based on admin status
if (is_admin) {
add_button.textContent = add_button.dataset.defaultText;
add_button.addEventListener('click', function () {
add_to_lidarr(artist.Name, add_button);
});
} else {
add_button.textContent = 'Request';
add_button.addEventListener('click', function () {
request_artist(artist.Name, add_button);
});
}
artist_col
.querySelector('.get-preview-btn')
.addEventListener('click', function () {
preview_req(artist.Name);
});
// Listen to Sample button logic
artist_col
.querySelector('.listen-sample-btn')
.addEventListener('click', function () {
listenSampleReq(artist.Name);
});
artist_col.querySelector('.followers').textContent = artist.Followers;
artist_col.querySelector('.popularity').textContent = artist.Popularity;
var statusValue = 'info';
if (
artist.Status === 'Added' ||
artist.Status === 'Already in Lidarr'
) {
statusValue = 'success';
add_button.classList.remove('btn-primary');
add_button.classList.add('btn-secondary');
add_button.disabled = true;
add_button.textContent = artist.Status;
} else if (artist.Status === 'Requested') {
statusValue = 'warning';
add_button.classList.remove('btn-primary');
add_button.classList.add('btn-warning');
add_button.disabled = true;
add_button.textContent = 'Pending Approval';
} else if (
artist.Status === 'Failed to Add' ||
artist.Status === 'Invalid Path' ||
artist.Status === 'Rejected'
) {
statusValue = 'danger';
add_button.classList.remove('btn-primary');
add_button.classList.add('btn-danger');
add_button.disabled = true;
add_button.textContent = artist.Status;
} else {
statusValue = 'info';
}
if (statusDot) {
statusDot.dataset.status = statusValue;
}
artist_row.appendChild(clone);
});
if (initialLoadComplete) {
create_load_more_button();
}
}
// Remove infinite scroll triggers
window.removeEventListener('scroll', function () {});
window.removeEventListener('touchmove', function () {});
window.removeEventListener('touchend', function () {});
function add_to_lidarr(artist_name, buttonEl) {
if (socket.connected) {
if (buttonEl) {
buttonEl.disabled = true;
buttonEl.innerHTML =
'Adding...';
buttonEl.classList.remove('btn-primary', 'btn-danger');
if (!buttonEl.classList.contains('btn-secondary')) {
buttonEl.classList.add('btn-secondary');
}
buttonEl.dataset.loading = 'true';
}
socket.emit('adder', encodeURIComponent(artist_name));
} else {
show_toast('Connection Lost', 'Please reload to continue.');
}
}
function request_artist(artist_name, buttonEl) {
if (socket.connected) {
if (buttonEl) {
buttonEl.disabled = true;
buttonEl.innerHTML =
'Requesting...';
buttonEl.classList.remove('btn-primary', 'btn-danger');
if (!buttonEl.classList.contains('btn-secondary')) {
buttonEl.classList.add('btn-secondary');
}
buttonEl.dataset.loading = 'true';
}
socket.emit('request_artist', encodeURIComponent(artist_name));
} else {
show_toast('Connection Lost', 'Please reload to continue.');
}
}
function show_toast(header, message) {
var toast_container = document.querySelector('.toast-container');
var toast_template = document
.getElementById('toast-template')
.cloneNode(true);
toast_template.classList.remove('d-none');
toast_template.querySelector('.toast-header strong').textContent = header;
toast_template.querySelector('.toast-body').textContent = message;
toast_template.querySelector('.text-muted').textContent =
new Date().toLocaleString();
toast_container.appendChild(toast_template);
var toast = new bootstrap.Toast(toast_template);
toast.show();
toast_template.addEventListener('hidden.bs.toast', function () {
toast_template.remove();
});
}
return_to_top.addEventListener('click', function () {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
lidarr_select_all_checkbox.addEventListener('change', function () {
var is_checked = this.checked;
var checkboxes = document.querySelectorAll('input[name="lidarr-item"]');
checkboxes.forEach(function (checkbox) {
checkbox.checked = is_checked;
});
});
lidarr_get_artists_button.addEventListener('click', function () {
lidarr_get_artists_button.disabled = true;
lidarr_spinner.classList.remove('d-none');
lidarr_status.textContent = 'Accessing Lidarr API';
lidarr_item_list.innerHTML = '';
socket.emit('get_lidarr_artists');
});
start_stop_button.addEventListener('click', function () {
var running_state =
start_stop_button.textContent.trim() === START_LABEL ? true : false;
if (running_state) {
// Reset initial load state and show overlay until first results arrive
initialLoadComplete = false;
initialLoadHasMore = false;
loadMorePending = false;
show_header_spinner();
remove_load_more_button();
start_stop_button.classList.remove('btn-success');
start_stop_button.classList.add('btn-warning');
start_stop_button.textContent = STOP_LABEL;
var checked_items = Array.from(
document.querySelectorAll('input[name="lidarr-item"]:checked')
).map((item) => item.value);
document
.querySelectorAll('input[name="lidarr-item"]')
.forEach((item) => {
item.disabled = true;
});
lidarr_get_artists_button.disabled = true;
lidarr_select_all_checkbox.disabled = true;
socket.emit('start_req', checked_items);
} else {
hide_header_spinner();
start_stop_button.classList.add('btn-success');
start_stop_button.classList.remove('btn-warning');
start_stop_button.textContent = START_LABEL;
document
.querySelectorAll('input[name="lidarr-item"]')
.forEach((item) => {
item.disabled = false;
});
lidarr_get_artists_button.disabled = false;
lidarr_select_all_checkbox.disabled = false;
socket.emit('stop_req');
}
});
if (load_more_button) {
load_more_button.addEventListener('click', function () {
if (loadMorePending || load_more_button.disabled) {
return;
}
loadMorePending = true;
load_more_button.disabled = true;
show_header_spinner();
socket.emit('load_more_artists');
});
}
function build_settings_payload() {
return {
lidarr_address: lidarr_address ? lidarr_address.value : '',
lidarr_api_key: lidarr_api_key ? lidarr_api_key.value : '',
root_folder_path: root_folder_path ? root_folder_path.value : '',
youtube_api_key: youtube_api_key ? youtube_api_key.value : '',
openai_api_key: openai_api_key_input ? openai_api_key_input.value : '',
openai_model: openai_model_input ? openai_model_input.value : '',
openai_max_seed_artists: openai_max_seed_artists_input
? openai_max_seed_artists_input.value
: '',
similar_artist_batch_size: similar_artist_batch_size_input
? similar_artist_batch_size_input.value
: '',
quality_profile_id: quality_profile_id_input
? quality_profile_id_input.value
: '',
metadata_profile_id: metadata_profile_id_input
? metadata_profile_id_input.value
: '',
lidarr_api_timeout: lidarr_api_timeout_input
? lidarr_api_timeout_input.value
: '',
fallback_to_top_result: fallback_to_top_result_checkbox
? fallback_to_top_result_checkbox.checked
: false,
search_for_missing_albums: search_for_missing_albums_checkbox
? search_for_missing_albums_checkbox.checked
: false,
dry_run_adding_to_lidarr: dry_run_adding_to_lidarr_checkbox
? dry_run_adding_to_lidarr_checkbox.checked
: false,
auto_start: auto_start_checkbox ? auto_start_checkbox.checked : false,
auto_start_delay: auto_start_delay_input
? auto_start_delay_input.value
: '',
last_fm_api_key: last_fm_api_key_input
? last_fm_api_key_input.value
: '',
last_fm_api_secret: last_fm_api_secret_input
? last_fm_api_secret_input.value
: '',
api_key: api_key_input ? api_key_input.value : '',
};
}
function populate_settings_form(settings) {
if (!settings) {
return;
}
if (lidarr_address) {
lidarr_address.value = settings.lidarr_address || '';
}
if (lidarr_api_key) {
lidarr_api_key.value = settings.lidarr_api_key || '';
}
if (root_folder_path) {
root_folder_path.value = settings.root_folder_path || '';
}
if (youtube_api_key) {
youtube_api_key.value = settings.youtube_api_key || '';
}
if (quality_profile_id_input) {
const qualityProfile = settings.quality_profile_id;
quality_profile_id_input.value =
qualityProfile === undefined || qualityProfile === null
? ''
: qualityProfile;
}
if (metadata_profile_id_input) {
const metadataProfile = settings.metadata_profile_id;
metadata_profile_id_input.value =
metadataProfile === undefined || metadataProfile === null
? ''
: metadataProfile;
}
if (lidarr_api_timeout_input) {
const apiTimeout = settings.lidarr_api_timeout;
lidarr_api_timeout_input.value =
apiTimeout === undefined || apiTimeout === null ? '' : apiTimeout;
}
if (fallback_to_top_result_checkbox) {
fallback_to_top_result_checkbox.checked = Boolean(
settings.fallback_to_top_result
);
}
if (search_for_missing_albums_checkbox) {
search_for_missing_albums_checkbox.checked = Boolean(
settings.search_for_missing_albums
);
}
if (dry_run_adding_to_lidarr_checkbox) {
dry_run_adding_to_lidarr_checkbox.checked = Boolean(
settings.dry_run_adding_to_lidarr
);
}
if (similar_artist_batch_size_input) {
const batchSize = settings.similar_artist_batch_size;
similar_artist_batch_size_input.value =
batchSize === undefined || batchSize === null ? '' : batchSize;
}
if (auto_start_checkbox) {
auto_start_checkbox.checked = Boolean(settings.auto_start);
}
if (auto_start_delay_input) {
const autoStartDelay = settings.auto_start_delay;
auto_start_delay_input.value =
autoStartDelay === undefined || autoStartDelay === null
? ''
: autoStartDelay;
}
if (last_fm_api_key_input) {
last_fm_api_key_input.value = settings.last_fm_api_key || '';
}
if (last_fm_api_secret_input) {
last_fm_api_secret_input.value = settings.last_fm_api_secret || '';
}
if (api_key_input) {
api_key_input.value = settings.api_key || '';
}
if (openai_api_key_input) {
openai_api_key_input.value = settings.openai_api_key || '';
}
if (openai_model_input) {
openai_model_input.value = settings.openai_model || '';
}
if (openai_max_seed_artists_input) {
const maxSeedArtists = settings.openai_max_seed_artists;
openai_max_seed_artists_input.value =
maxSeedArtists === undefined || maxSeedArtists === null
? ''
: maxSeedArtists;
}
}
function handle_settings_saved(payload) {
if (save_changes_button) {
save_changes_button.disabled = false;
}
if (save_message) {
save_message.classList.remove('alert-danger');
if (!save_message.classList.contains('alert-success')) {
save_message.classList.add('alert-success');
}
save_message.classList.remove('d-none');
save_message.textContent =
(payload && payload.message) || 'Settings saved successfully.';
}
show_toast(
'Settings saved',
(payload && payload.message) || 'Configuration updated successfully.'
);
}
function handle_settings_save_error(payload) {
if (save_changes_button) {
save_changes_button.disabled = false;
}
const message =
(payload && payload.message) ||
'Saving settings failed. Check the logs for more details.';
if (save_message) {
save_message.classList.remove('d-none');
save_message.classList.remove('alert-success');
save_message.classList.add('alert-danger');
save_message.textContent = message;
}
show_toast('Settings error', message);
}
function reset_save_message() {
if (!save_message) {
return;
}
save_message.classList.add('d-none');
save_message.classList.remove('alert-danger');
if (!save_message.classList.contains('alert-success')) {
save_message.classList.add('alert-success');
}
save_message.textContent = 'Settings saved successfully.';
}
if (settings_form && config_modal) {
settings_form.addEventListener('submit', (event) => {
event.preventDefault();
if (!socket.connected) {
show_toast('Connection Lost', 'Please reconnect to continue.');
return;
}
reset_save_message();
if (save_changes_button) {
save_changes_button.disabled = true;
}
socket.emit('update_settings', build_settings_payload());
});
const handle_modal_show = () => {
reset_save_message();
if (save_changes_button) {
save_changes_button.disabled = false;
}
socket.on('settingsLoaded', populate_settings_form);
socket.emit('load_settings');
};
const handle_modal_hidden = () => {
socket.off('settingsLoaded', populate_settings_form);
reset_save_message();
if (save_changes_button) {
save_changes_button.disabled = false;
}
};
config_modal.addEventListener('show.bs.modal', handle_modal_show);
config_modal.addEventListener('hidden.bs.modal', handle_modal_hidden);
socket.on('settingsSaved', handle_settings_saved);
socket.on('settingsSaveError', handle_settings_save_error);
}
lidarr_sidebar.addEventListener('show.bs.offcanvas', function (event) {
socket.emit('side_bar_opened');
socket.emit('personal_sources_poll');
});
socket.on('lidarr_sidebar_update', (response) => {
if (response.Status == 'Success') {
lidarr_status.textContent = 'Lidarr List Retrieved';
lidarr_items = response.Data;
lidarr_item_list.innerHTML = '';
lidarr_select_all_container.classList.remove('d-none');
for (var i = 0; i < lidarr_items.length; i++) {
var item = lidarr_items[i];
var div = document.createElement('div');
div.className = 'form-check';
var input = document.createElement('input');
input.type = 'checkbox';
input.className = 'form-check-input';
input.id = 'lidarr-' + i;
input.name = 'lidarr-item';
input.value = item.name;
if (item.checked) {
input.checked = true;
}
var label = document.createElement('label');
label.className = 'form-check-label';
label.htmlFor = 'lidarr-' + i;
label.textContent = item.name;
input.addEventListener('change', function () {
check_if_all_selected();
});
div.appendChild(input);
div.appendChild(label);
lidarr_item_list.appendChild(div);
}
} else {
lidarr_status.textContent = response.Code;
}
lidarr_get_artists_button.disabled = false;
lidarr_spinner.classList.add('d-none');
load_lidarr_data(response);
if (!response.Running) {
hide_header_spinner();
}
});
socket.on('refresh_artist', (artist) => {
var artist_cards = document.querySelectorAll('#artist-column');
artist_cards.forEach(function (card) {
var cardEl = card.querySelector('.artist-card');
if (!cardEl) {
return;
}
var titleEl = cardEl.querySelector('.card-title');
var card_artist_name = titleEl ? titleEl.textContent.trim() : '';
if (card_artist_name === artist.Name) {
var add_button = cardEl.querySelector('.add-to-lidarr-btn');
var statusDot = cardEl.querySelector('.led');
var statusValue = 'info';
if (
artist.Status === 'Added' ||
artist.Status === 'Already in Lidarr'
) {
statusValue = 'success';
add_button.classList.remove('btn-primary');
add_button.classList.add('btn-secondary');
add_button.disabled = true;
add_button.innerHTML = artist.Status;
add_button.dataset.loading = '';
} else if (artist.Status === 'Requested') {
statusValue = 'warning';
add_button.classList.remove('btn-primary');
add_button.classList.add('btn-warning');
add_button.disabled = true;
add_button.innerHTML = 'Pending Approval';
add_button.dataset.loading = '';
} else if (
artist.Status === 'Failed to Add' ||
artist.Status === 'Invalid Path' ||
artist.Status === 'Rejected'
) {
statusValue = 'danger';
add_button.classList.remove('btn-primary');
add_button.classList.add('btn-danger');
add_button.disabled = true;
add_button.innerHTML = artist.Status;
add_button.dataset.loading = '';
} else {
statusValue = 'info';
add_button.disabled = false;
add_button.classList.remove('btn-danger', 'btn-secondary');
if (!add_button.classList.contains('btn-primary')) {
add_button.classList.add('btn-primary');
}
add_button.innerHTML =
add_button.dataset.defaultText || 'Add to Lidarr';
add_button.dataset.loading = '';
}
if (statusDot) {
statusDot.dataset.status = statusValue;
}
return;
}
});
});
socket.on('more_artists_loaded', function (data) {
append_artists(data);
});
socket.on('ai_prompt_ack', function (payload) {
set_ai_form_loading(false);
if (payload && Array.isArray(payload.seeds) && payload.seeds.length > 0) {
var listItems = payload.seeds
.map(function (seed) {
return `${escape_html(seed)}`;
})
.join('');
if (ai_helper_results) {
ai_helper_results.innerHTML = `AI picked these seed artists:`;
ai_helper_results.classList.remove('d-none');
}
show_toast(
'AI Discovery',
'Working from fresh seed artists suggested by the assistant.'
);
} else if (ai_helper_results) {
ai_helper_results.textContent =
"AI discovery started. We'll surface artists as soon as we find them.";
ai_helper_results.classList.remove('d-none');
}
});
socket.on('ai_prompt_error', function (payload) {
set_ai_form_loading(false);
var message =
payload && payload.message
? payload.message
: 'We could not complete the AI request right now.';
if (ai_helper_error) {
ai_helper_error.textContent = message;
ai_helper_error.classList.remove('d-none');
}
hide_header_spinner();
show_toast('AI Assistant', message);
});
socket.on('personal_sources_state', function (state) {
personalSourcesState = state || {};
updatePersonalButtons();
});
socket.on('user_recs_ack', function (payload) {
var source =
payload && payload.source ? String(payload.source).toLowerCase() : '';
var username = payload && payload.username ? payload.username : '';
var seeds = payload && Array.isArray(payload.seeds) ? payload.seeds : [];
var title =
source === 'lastfm' ? 'Last.fm discovery' : 'Personal discovery';
var message = '';
if (seeds.length > 0) {
message = 'Streaming ' + seeds.length + ' picks';
if (username) {
message += ' for ' + username;
}
message += '.';
} else {
message = 'Working from fresh personal recommendations.';
}
show_toast(title, message);
});
socket.on('user_recs_error', function (payload) {
var source =
payload && payload.source ? String(payload.source).toLowerCase() : '';
var message =
payload && payload.message
? payload.message
: 'We could not fetch your personal recommendations right now.';
hide_header_spinner();
setPersonalDiscoveryLoading(null, false);
var title =
source === 'lastfm' ? 'Last.fm discovery' : 'Personal discovery';
show_toast(title, message);
});
// Server signals that initial batches are complete: show the Load More button now
socket.on('initial_load_complete', function (payload) {
initialLoadComplete = true;
initialLoadHasMore = !!(payload && payload.hasMore);
loadMorePending = false;
if (personalDiscoveryState.inFlight) {
setPersonalDiscoveryLoading(null, false);
}
hide_header_spinner();
if (initialLoadHasMore) {
create_load_more_button();
} else {
remove_load_more_button();
}
});
socket.on('load_more_complete', function (payload) {
loadMorePending = false;
initialLoadHasMore = !!(payload && payload.hasMore);
hide_header_spinner();
if (initialLoadHasMore) {
create_load_more_button();
} else {
remove_load_more_button();
}
});
socket.on('clear', function () {
clear_all();
});
socket.on('new_toast_msg', function (data) {
show_toast(data.title, data.message);
});
socket.on('disconnect', function () {
show_toast('Connection Lost', 'Please reconnect to continue.');
hide_header_spinner();
personalSourcesState = null;
setPersonalDiscoveryLoading(null, false);
clear_all();
});
function clear_all() {
var artist_row = document.getElementById('artist-row');
var artist_cards = artist_row.querySelectorAll('#artist-column');
artist_cards.forEach(function (card) {
card.remove();
});
remove_load_more_button();
initialLoadComplete = false;
initialLoadHasMore = false;
loadMorePending = false;
// spinner state is controlled by the caller
}
var preview_request_flag = false;
function preview_req(artist_name) {
if (!preview_request_flag) {
preview_request_flag = true;
show_bio_modal_loading(artist_name);
socket.emit('preview_req', encodeURIComponent(artist_name));
setTimeout(() => {
preview_request_flag = false;
}, 1500);
}
}
socket.on('lastfm_preview', function (preview_info) {
var modal_body = document.getElementById('modal-body');
var modal_title = document.getElementById('bio-modal-title');
var modalEl = document.getElementById('bio-modal-modal');
if (typeof preview_info === 'string') {
if (modal_body) {
var safeMessage = escape_html(preview_info);
modal_body.innerHTML = `${safeMessage}
`;
}
show_toast('Error Retrieving Bio', preview_info);
if (modalEl && !modalEl.classList.contains('show')) {
show_modal_with_lock('bio-modal-modal');
}
return;
}
var artist_name = preview_info.artist_name;
var biography = preview_info.biography;
if (modal_title) {
modal_title.textContent = artist_name;
}
if (modal_body) {
var biographyHtml = render_biography_html(biography);
if (biographyHtml) {
modal_body.innerHTML = biographyHtml;
} else {
modal_body.innerHTML =
'No formatted biography was returned for this artist.
';
}
}
if (modalEl && !modalEl.classList.contains('show')) {
show_modal_with_lock('bio-modal-modal');
}
});
// Listen Sample button event
function listenSampleReq(artist_name) {
show_audio_modal_loading(artist_name);
socket.emit('prehear_req', encodeURIComponent(artist_name));
}
socket.on('prehear_result', function (data) {
if (data && (data.videoId || data.previewUrl)) {
update_audio_modal_content(data);
} else {
var message =
data && data.error
? data.error
: 'No YouTube or audio preview found.';
show_audio_modal_error(message);
show_toast('No sample found', message);
}
});