mirror of
https://github.com/MrSleeps/jellyfin-scene-search.git
synced 2026-11-14 22:11:27 +00:00
Manage indexed libraries from Jellyfin plugin
This commit is contained in:
@@ -23,16 +23,13 @@
|
||||
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="password" id="apiKey" label="API Key" />
|
||||
<div class="fieldDescription">Must match SCENE_API_KEY in jellyfin-scene-search. Leave empty only if the API has no key configured.</div>
|
||||
<div class="fieldDescription">Must match SCENE_API_KEY in jellyfin-scene-search.</div>
|
||||
</div>
|
||||
|
||||
<label class="checkboxContainer">
|
||||
<input is="emby-checkbox" type="checkbox" id="requirePrefix" />
|
||||
<span>Require a search prefix</span>
|
||||
</label>
|
||||
<div class="fieldDescription" style="margin-left: 3em; margin-top: -1em; margin-bottom: 1em;">
|
||||
Recommended while only part of the Jellyfin library is indexed. When enabled, ordinary searches keep using Jellyfin's built-in search.
|
||||
</div>
|
||||
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="text" id="searchPrefix" label="Search Prefix" />
|
||||
@@ -45,9 +42,26 @@
|
||||
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="number" id="timeoutSeconds" min="5" max="300" step="1" label="API Timeout (seconds)" />
|
||||
<div class="fieldDescription">The first semantic search may take longer while SigLIP is loaded.</div>
|
||||
</div>
|
||||
|
||||
<h2>Indexed Libraries</h2>
|
||||
<div class="fieldDescription" style="margin-bottom:1em;">
|
||||
Choose which Jellyfin libraries Scene Search should index. Selection is stored in this plugin, not in the Python .env file.
|
||||
</div>
|
||||
<div id="libraryList">Loading libraries…</div>
|
||||
|
||||
<div style="margin-top:1em; margin-bottom:1em;">
|
||||
<button is="emby-button" type="button" id="indexNow" class="raised">
|
||||
<span>Index selected libraries</span>
|
||||
</button>
|
||||
<label class="checkboxContainer" style="margin-top:1em;">
|
||||
<input is="emby-checkbox" type="checkbox" id="forceIndex" />
|
||||
<span>Force rebuild already-indexed items</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="indexStatus" class="fieldDescription" style="margin-bottom:1.5em;"></div>
|
||||
|
||||
<button is="emby-button" type="submit" class="raised button-submit block">
|
||||
<span>Save</span>
|
||||
</button>
|
||||
@@ -59,10 +73,112 @@
|
||||
(function () {
|
||||
var pluginId = "2d8d1bb5-3f0c-4d5a-9d59-934463c06d35";
|
||||
var page = document.querySelector('#sceneSearchConfigPage');
|
||||
var currentConfig = null;
|
||||
var statusTimer = null;
|
||||
|
||||
function selectedLibraryIds() {
|
||||
return Array.prototype.slice.call(
|
||||
page.querySelectorAll('.sceneLibrary:checked')
|
||||
).map(function (el) { return el.value; });
|
||||
}
|
||||
|
||||
function renderLibraries(data) {
|
||||
var container = page.querySelector('#libraryList');
|
||||
var selected = new Set((currentConfig && currentConfig.SelectedLibraryIds) || []);
|
||||
var libraries = (data && data.libraries) || [];
|
||||
|
||||
if (!libraries.length) {
|
||||
container.textContent = 'No Jellyfin video libraries were returned by the Scene Search API.';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = libraries.map(function (library) {
|
||||
var checked = selected.has(library.id) ? ' checked' : '';
|
||||
var detail = library.indexed_items + ' indexed item' +
|
||||
(library.indexed_items === 1 ? '' : 's') + ', ' +
|
||||
library.indexed_frames + ' frames';
|
||||
return '<label class="checkboxContainer">' +
|
||||
'<input type="checkbox" class="sceneLibrary" value="' +
|
||||
library.id.replace(/"/g, '"') + '"' + checked + ' />' +
|
||||
'<span>' + library.name.replace(/</g, '<').replace(/>/g, '>') + '</span>' +
|
||||
'<div class="fieldDescription" style="margin-left:3em;">' + detail + '</div>' +
|
||||
'</label>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderStatus(status) {
|
||||
var container = page.querySelector('#indexStatus');
|
||||
if (!status) {
|
||||
container.textContent = 'Index status unavailable.';
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.running) {
|
||||
var parts = ['Indexing'];
|
||||
if (status.library) {
|
||||
parts.push(status.library);
|
||||
}
|
||||
if (status.item) {
|
||||
parts.push(status.item);
|
||||
}
|
||||
if (status.item_number && status.item_total) {
|
||||
parts.push(status.item_number + '/' + status.item_total);
|
||||
}
|
||||
if (status.frames_total) {
|
||||
parts.push((status.frames_processed || 0) + '/' + status.frames_total + ' frames');
|
||||
}
|
||||
if (status.stage === 'sync' || status.stage === 'syncing') {
|
||||
parts = ['Rebuilding search index'];
|
||||
if (status.documents_processed) {
|
||||
parts.push(status.documents_processed + ' documents');
|
||||
}
|
||||
}
|
||||
container.textContent = parts.join(' — ');
|
||||
} else if (status.stage === 'failed') {
|
||||
container.textContent = 'Index failed: ' + (status.error || 'unknown error');
|
||||
} else if (status.stage === 'complete') {
|
||||
container.textContent =
|
||||
'Index complete — ' + (status.indexed || 0) + ' indexed, ' +
|
||||
(status.skipped || 0) + ' unchanged, ' +
|
||||
(status.failed || 0) + ' failed, ' +
|
||||
(status.pruned || 0) + ' removed.';
|
||||
} else {
|
||||
container.textContent = 'Index idle.';
|
||||
}
|
||||
}
|
||||
|
||||
function refreshStatus() {
|
||||
return ApiClient.getJSON(ApiClient.getUrl('Plugins/SceneSearch/IndexStatus'))
|
||||
.then(function (status) {
|
||||
renderStatus(status);
|
||||
if (status && status.running) {
|
||||
if (!statusTimer) {
|
||||
statusTimer = setInterval(refreshStatus, 2000);
|
||||
}
|
||||
} else if (statusTimer) {
|
||||
clearInterval(statusTimer);
|
||||
statusTimer = null;
|
||||
loadLibraries();
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
renderStatus(null);
|
||||
});
|
||||
}
|
||||
|
||||
function loadLibraries() {
|
||||
return ApiClient.getJSON(ApiClient.getUrl('Plugins/SceneSearch/Libraries'))
|
||||
.then(renderLibraries)
|
||||
.catch(function () {
|
||||
page.querySelector('#libraryList').textContent =
|
||||
'Could not load libraries. Check the Scene Search API URL/key and that the Python service is running.';
|
||||
});
|
||||
}
|
||||
|
||||
page.addEventListener('pageshow', function () {
|
||||
Dashboard.showLoadingMsg();
|
||||
ApiClient.getPluginConfiguration(pluginId).then(function (config) {
|
||||
currentConfig = config;
|
||||
page.querySelector('#enabled').checked = config.Enabled;
|
||||
page.querySelector('#apiUrl').value = config.ApiUrl || 'http://127.0.0.1:8097';
|
||||
page.querySelector('#apiKey').value = config.ApiKey || '';
|
||||
@@ -70,10 +186,44 @@
|
||||
page.querySelector('#searchPrefix').value = config.SearchPrefix || 'scene:';
|
||||
page.querySelector('#maxResults').value = config.MaxResults || 20;
|
||||
page.querySelector('#timeoutSeconds').value = config.TimeoutSeconds || 60;
|
||||
|
||||
return Promise.all([loadLibraries(), refreshStatus()]);
|
||||
}).finally(function () {
|
||||
Dashboard.hideLoadingMsg();
|
||||
});
|
||||
});
|
||||
|
||||
page.addEventListener('pagehide', function () {
|
||||
if (statusTimer) {
|
||||
clearInterval(statusTimer);
|
||||
statusTimer = null;
|
||||
}
|
||||
});
|
||||
|
||||
page.querySelector('#indexNow').addEventListener('click', function () {
|
||||
var ids = selectedLibraryIds();
|
||||
if (!ids.length) {
|
||||
page.querySelector('#indexStatus').textContent = 'Select at least one library first.';
|
||||
return;
|
||||
}
|
||||
|
||||
page.querySelector('#indexStatus').textContent = 'Starting index…';
|
||||
ApiClient.ajax({
|
||||
type: 'POST',
|
||||
url: ApiClient.getUrl('Plugins/SceneSearch/Index'),
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
library_ids: ids,
|
||||
force: page.querySelector('#forceIndex').checked
|
||||
})
|
||||
}).then(function () {
|
||||
refreshStatus();
|
||||
}).catch(function () {
|
||||
page.querySelector('#indexStatus').textContent =
|
||||
'Could not start indexing. Check Jellyfin logs and Scene Search API status.';
|
||||
});
|
||||
});
|
||||
|
||||
page.querySelector('.sceneSearchConfigForm').addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
Dashboard.showLoadingMsg();
|
||||
@@ -86,6 +236,7 @@
|
||||
config.SearchPrefix = page.querySelector('#searchPrefix').value.trim();
|
||||
config.MaxResults = parseInt(page.querySelector('#maxResults').value, 10) || 20;
|
||||
config.TimeoutSeconds = parseInt(page.querySelector('#timeoutSeconds').value, 10) || 60;
|
||||
config.SelectedLibraryIds = selectedLibraryIds();
|
||||
|
||||
return ApiClient.updatePluginConfiguration(pluginId, config);
|
||||
}).then(function (result) {
|
||||
|
||||
Reference in New Issue
Block a user