mirror of
https://github.com/zotero/zotero.git
synced 2026-10-08 03:08:19 +00:00
minor cleanup of prefs UI
Some checks are pending
Some checks are pending
- hide the diagnostics by default - do not show the chunks count next to progress bars to avoid confusion - shorten labels to be more concise
This commit is contained in:
parent
267e779c8f
commit
3e2f7fb33c
7 changed files with 237 additions and 175 deletions
|
|
@ -33,7 +33,7 @@
|
|||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
data-l10n-id="preferences-advanced-semantic-search-endpoint-dialog"
|
||||
onload="Zotero_EmbeddingsEndpointDialog.init()">
|
||||
onload="init()">
|
||||
<dialog
|
||||
id="zotero-embeddings-endpoint"
|
||||
orient="vertical"
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
<separator class="thin"/>
|
||||
|
||||
<label data-l10n-id="preferences-advanced-semantic-search-endpoint-url" control="endpoint-url"/>
|
||||
<html:input type="text" id="endpoint-url" placeholder="http://localhost:8080/v1/embeddings"/>
|
||||
<html:input type="text" id="endpoint-url"/>
|
||||
<description class="endpoint-note"
|
||||
data-l10n-id="preferences-advanced-semantic-search-endpoint-privacy"/>
|
||||
<separator class="thin"/>
|
||||
|
|
@ -82,98 +82,101 @@
|
|||
</vbox>
|
||||
|
||||
<script>
|
||||
<![CDATA[
|
||||
var Zotero_EmbeddingsEndpointDialog = {
|
||||
init() {
|
||||
this.io = window.arguments[0];
|
||||
this.urlInput = document.getElementById('endpoint-url');
|
||||
this.dialog = document.getElementById('zotero-embeddings-endpoint');
|
||||
this.urlInput.value = Zotero.Prefs.get('embeddings.endpoint') || '';
|
||||
document.l10n.setAttributes(this.dialog.getButton('accept'),
|
||||
'preferences-advanced-semantic-search-endpoint-accept');
|
||||
// Offered only while there is an endpoint to remove
|
||||
let remove = this.dialog.getButton('extra2');
|
||||
document.l10n.setAttributes(remove, 'preferences-advanced-semantic-search-endpoint-remove');
|
||||
remove.hidden = !this.urlInput.value;
|
||||
document.addEventListener('dialogextra2', () => this.remove());
|
||||
let io;
|
||||
let urlInput;
|
||||
let dialog;
|
||||
|
||||
let serving = Zotero.Embeddings.getServing();
|
||||
document.getElementById('endpoint-model').value = Zotero.Embeddings.getModelName();
|
||||
document.getElementById('endpoint-pooling').value = Zotero.Embeddings.getPooling();
|
||||
document.getElementById('endpoint-file').value = `${serving.gguf} (${serving.quant})`;
|
||||
let { command, url } = Zotero.Embeddings.Endpoint.getCommand();
|
||||
document.getElementById('endpoint-command').value = command;
|
||||
document.l10n.setAttributes(document.getElementById('endpoint-command-url'),
|
||||
'preferences-advanced-semantic-search-endpoint-then-use', { url });
|
||||
document.getElementById('endpoint-copy').addEventListener('command', () => {
|
||||
Zotero.Utilities.Internal.copyTextToClipboard(command);
|
||||
});
|
||||
function init() {
|
||||
io = window.arguments[0];
|
||||
urlInput = document.getElementById('endpoint-url');
|
||||
dialog = document.getElementById('zotero-embeddings-endpoint');
|
||||
let { command, url } = Zotero.Embeddings.Endpoint.getCommand();
|
||||
let stored = Zotero.Prefs.get('embeddings.endpoint');
|
||||
// Where the suggested command serves, so a server started as
|
||||
// suggested is accepted as is
|
||||
urlInput.value = stored || url;
|
||||
document.l10n.setAttributes(dialog.getButton('accept'),
|
||||
'preferences-advanced-semantic-search-endpoint-accept');
|
||||
// Offered only while there is an endpoint to remove
|
||||
let remove = dialog.getButton('extra2');
|
||||
document.l10n.setAttributes(remove, 'preferences-advanced-semantic-search-endpoint-remove');
|
||||
remove.hidden = !stored;
|
||||
document.addEventListener('dialogextra2', removeEndpoint);
|
||||
|
||||
// Verification is asynchronous: hold the dialog open until it
|
||||
// has an answer
|
||||
document.addEventListener('dialogaccept', (event) => {
|
||||
event.preventDefault();
|
||||
this.accept();
|
||||
});
|
||||
this.urlInput.addEventListener('input', () => this.showError(null));
|
||||
this.urlInput.focus();
|
||||
},
|
||||
let serving = Zotero.Embeddings.getServing();
|
||||
document.getElementById('endpoint-model').value = Zotero.Embeddings.getModelName();
|
||||
document.getElementById('endpoint-pooling').value = Zotero.Embeddings.getPooling();
|
||||
document.getElementById('endpoint-file').value = `${serving.gguf} (${serving.quant})`;
|
||||
document.getElementById('endpoint-command').value = command;
|
||||
document.l10n.setAttributes(document.getElementById('endpoint-command-url'),
|
||||
'preferences-advanced-semantic-search-endpoint-then-use', { url });
|
||||
document.getElementById('endpoint-copy').addEventListener('command', () => {
|
||||
Zotero.Utilities.Internal.copyTextToClipboard(command);
|
||||
});
|
||||
|
||||
// Stop using the endpoint. Nothing stored is affected: the vectors
|
||||
// it produced match the model's, and a later endpoint verifies anew.
|
||||
remove() {
|
||||
Zotero.Prefs.clear('embeddings.endpoint');
|
||||
this.io.ok = true;
|
||||
window.close();
|
||||
},
|
||||
// Verification is asynchronous: hold the dialog open until it
|
||||
// has an answer
|
||||
document.addEventListener('dialogaccept', (event) => {
|
||||
event.preventDefault();
|
||||
accept();
|
||||
});
|
||||
urlInput.addEventListener('input', () => showError(null));
|
||||
urlInput.focus();
|
||||
}
|
||||
|
||||
async accept() {
|
||||
let url = this.urlInput.value.trim();
|
||||
// An emptied URL turns the endpoint off; nothing to verify
|
||||
if (!url) {
|
||||
this.remove();
|
||||
return;
|
||||
}
|
||||
this.setBusy(true);
|
||||
this.showError(null);
|
||||
let verdict;
|
||||
try {
|
||||
verdict = await Zotero.Embeddings.Endpoint.verify(url);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
verdict = { state: 'unreachable' };
|
||||
}
|
||||
this.setBusy(false);
|
||||
if (verdict.state != 'ok') {
|
||||
this.showError(verdict);
|
||||
return;
|
||||
}
|
||||
Zotero.Prefs.set('embeddings.endpoint', url);
|
||||
this.io.ok = true;
|
||||
window.close();
|
||||
},
|
||||
// Stop using the endpoint. Nothing stored is affected: the vectors
|
||||
// it produced match the model's, and a later endpoint verifies anew.
|
||||
function removeEndpoint() {
|
||||
Zotero.Prefs.clear('embeddings.endpoint');
|
||||
io.ok = true;
|
||||
window.close();
|
||||
}
|
||||
|
||||
setBusy(busy) {
|
||||
document.getElementById('endpoint-progress').hidden = !busy;
|
||||
this.dialog.getButton('accept').disabled = busy;
|
||||
this.dialog.getButton('cancel').disabled = busy;
|
||||
this.dialog.getButton('extra2').disabled = busy;
|
||||
this.urlInput.disabled = busy;
|
||||
},
|
||||
|
||||
showError(verdict) {
|
||||
let box = document.getElementById('endpoint-error');
|
||||
box.hidden = !verdict;
|
||||
if (!verdict) {
|
||||
return;
|
||||
}
|
||||
document.l10n.setAttributes(box,
|
||||
`preferences-advanced-semantic-search-endpoint-error-${verdict.state}`,
|
||||
{ pooling: Zotero.Embeddings.getPooling() });
|
||||
async function accept() {
|
||||
let url = urlInput.value.trim();
|
||||
// An emptied URL turns the endpoint off; nothing to verify
|
||||
if (!url) {
|
||||
removeEndpoint();
|
||||
return;
|
||||
}
|
||||
};
|
||||
]]>
|
||||
setBusy(true);
|
||||
showError(null);
|
||||
let verdict;
|
||||
try {
|
||||
verdict = await Zotero.Embeddings.Endpoint.verify(url);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
verdict = { state: 'unreachable' };
|
||||
}
|
||||
setBusy(false);
|
||||
if (verdict.state != 'ok') {
|
||||
showError(verdict);
|
||||
return;
|
||||
}
|
||||
Zotero.Prefs.set('embeddings.endpoint', url);
|
||||
io.ok = true;
|
||||
window.close();
|
||||
}
|
||||
|
||||
function setBusy(busy) {
|
||||
document.getElementById('endpoint-progress').hidden = !busy;
|
||||
dialog.getButton('accept').disabled = busy;
|
||||
dialog.getButton('cancel').disabled = busy;
|
||||
dialog.getButton('extra2').disabled = busy;
|
||||
urlInput.disabled = busy;
|
||||
}
|
||||
|
||||
function showError(verdict) {
|
||||
let box = document.getElementById('endpoint-error');
|
||||
box.hidden = !verdict;
|
||||
if (!verdict) {
|
||||
return;
|
||||
}
|
||||
document.l10n.setAttributes(box,
|
||||
`preferences-advanced-semantic-search-endpoint-error-${verdict.state}`,
|
||||
{ pooling: Zotero.Embeddings.getPooling() });
|
||||
}
|
||||
</script>
|
||||
</dialog>
|
||||
</window>
|
||||
|
|
|
|||
|
|
@ -113,6 +113,15 @@ Zotero_Preferences.Advanced = {
|
|||
document.getElementById('semantic-search-endpoint-configure').addEventListener('command', () => {
|
||||
this.openSemanticSearchEndpointDialog();
|
||||
});
|
||||
|
||||
let diagnostics = document.getElementById('semantic-search-diagnostics');
|
||||
let toggle = document.getElementById('semantic-search-diagnostics-toggle');
|
||||
toggle.addEventListener('command', () => {
|
||||
diagnostics.hidden = !diagnostics.hidden;
|
||||
document.l10n.setAttributes(toggle, diagnostics.hidden
|
||||
? 'preferences-advanced-semantic-search-diagnostics-show'
|
||||
: 'preferences-advanced-semantic-search-diagnostics-hide');
|
||||
});
|
||||
// The endpoint's stored verdict is read lazily; have it in memory
|
||||
// before the status line first renders
|
||||
Zotero.Embeddings.Endpoint.load().then(() => {
|
||||
|
|
@ -166,12 +175,11 @@ Zotero_Preferences.Advanced = {
|
|||
statusBox.hidden = !status.enabled;
|
||||
// Fulltext indexing only means something with a model selected
|
||||
document.getElementById('semantic-search-index-fulltext').disabled = !status.enabled;
|
||||
this.updateSemanticSearchEndpointUI(status.endpoint, status.enabled);
|
||||
if (!status.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateSemanticSearchEndpointUI(status.endpoint);
|
||||
|
||||
// Phase / status message
|
||||
let phaseLabel = document.getElementById('semantic-search-phase');
|
||||
let hasRemaining = status.items.done < status.items.total
|
||||
|
|
@ -242,15 +250,16 @@ Zotero_Preferences.Advanced = {
|
|||
document.getElementById('semantic-search-attachments-row').hidden
|
||||
= !Zotero.Prefs.get('embeddings.indexFulltext');
|
||||
this._updateSemanticSearchBar('attachments', status.chunks);
|
||||
this._updateSemanticSearchDiagnostics(status.diagnostics, status.eta);
|
||||
this._updateSemanticSearchDiagnostics(status);
|
||||
},
|
||||
|
||||
|
||||
// Whether the active model can be served at all, and how the configured
|
||||
// server stands (see Zotero.Embeddings.Endpoint.getStatus())
|
||||
updateSemanticSearchEndpointUI: function (endpoint) {
|
||||
updateSemanticSearchEndpointUI: function (endpoint, enabled) {
|
||||
let row = document.getElementById('semantic-search-endpoint-row');
|
||||
row.hidden = !Zotero.Embeddings.Endpoint.isSupported();
|
||||
// Nothing can be said about serving without a model
|
||||
row.hidden = !enabled || !Zotero.Embeddings.Endpoint.isSupported();
|
||||
if (row.hidden) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -281,7 +290,7 @@ Zotero_Preferences.Advanced = {
|
|||
|
||||
// Key/value rows of pipeline diagnostics for developers, so the labels
|
||||
// are plain English rather than localized
|
||||
_updateSemanticSearchDiagnostics: function (diagnostics, eta) {
|
||||
_updateSemanticSearchDiagnostics: function ({ items, chunks: indexed, diagnostics, eta }) {
|
||||
let n = (value, digits = 0) => (value ?? 0).toLocaleString(undefined, {
|
||||
maximumFractionDigits: digits, minimumFractionDigits: digits
|
||||
});
|
||||
|
|
@ -301,6 +310,8 @@ Zotero_Preferences.Advanced = {
|
|||
|
||||
let rows = [];
|
||||
let { window, run, engine, processes, slice, chunks } = diagnostics;
|
||||
rows.push(['Items indexed', `${n(items.done)} / ${n(items.total)}`]);
|
||||
rows.push(['Attachment chunks indexed', `${n(indexed.done)} / ${n(indexed.total)}`]);
|
||||
rows.push(['ETA', eta === null ? '—' : duration(eta)]);
|
||||
rows.push(['Throughput (2 min)', window ? speed(window) : '—']);
|
||||
rows.push(['Inference speed (run)', run ? speed(run) : '—']);
|
||||
|
|
@ -341,8 +352,8 @@ Zotero_Preferences.Advanced = {
|
|||
},
|
||||
|
||||
|
||||
// Fill one progress row with done out of total. The percentage is
|
||||
// rounded down, so it reads 100% only when everything is done.
|
||||
// Fill one progress bar. The percentage is rounded down, so it reads
|
||||
// 100% only when everything is done.
|
||||
_updateSemanticSearchBar: function (name, { done, total }) {
|
||||
let bar = document.getElementById(`semantic-search-${name}-progress`);
|
||||
bar.max = Math.max(total, 1);
|
||||
|
|
@ -350,7 +361,7 @@ Zotero_Preferences.Advanced = {
|
|||
document.l10n.setAttributes(
|
||||
document.getElementById(`semantic-search-${name}-value`),
|
||||
'preferences-advanced-semantic-search-progress-value',
|
||||
{ done, total, percent: total ? Math.floor(done / total * 100) : 0 }
|
||||
{ percent: total ? Math.floor(done / total * 100) : 0 }
|
||||
);
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -309,37 +309,27 @@
|
|||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
<description id="semantic-search-model-description"
|
||||
data-l10n-id="preferences-advanced-semantic-search-model-description"/>
|
||||
<!-- Temporary, for testing: pin best-match search to one engine -->
|
||||
<hbox align="center">
|
||||
<label data-l10n-id="preferences-advanced-best-match-engine"
|
||||
control="best-match-engine"/>
|
||||
<menulist id="best-match-engine"
|
||||
preference="extensions.zotero.search.bestMatchEngine"
|
||||
native="true">
|
||||
<menupopup>
|
||||
<menuitem value="hybrid"
|
||||
data-l10n-id="preferences-advanced-best-match-engine-hybrid"/>
|
||||
<menuitem value="lexical"
|
||||
data-l10n-id="preferences-advanced-best-match-engine-lexical"/>
|
||||
<menuitem value="semantic"
|
||||
data-l10n-id="preferences-advanced-best-match-engine-semantic"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
<hbox align="center">
|
||||
<label data-l10n-id="preferences-advanced-best-match-margin"
|
||||
control="best-match-margin"/>
|
||||
<html:input id="best-match-margin" class="html-input" type="number"
|
||||
min="1" max="100" step="1" size="3"
|
||||
data-preference="search.bestMatchMargin"/>
|
||||
</hbox>
|
||||
<checkbox id="semantic-search-index-fulltext"
|
||||
data-l10n-id="preferences-advanced-semantic-search-index-fulltext"
|
||||
preference="extensions.zotero.embeddings.indexFulltext"
|
||||
native="true"/>
|
||||
<hbox align="center">
|
||||
<label data-l10n-id="preferences-advanced-best-match-margin"
|
||||
control="best-match-margin"/>
|
||||
<html:input id="best-match-margin" class="html-input" type="number"
|
||||
min="0" max="100" step="1" size="3"
|
||||
data-preference="search.bestMatchMargin"/>
|
||||
</hbox>
|
||||
<description class="semantic-search-hint"
|
||||
data-l10n-id="preferences-advanced-best-match-margin-description"/>
|
||||
<hbox id="semantic-search-endpoint-row" align="center" hidden="true">
|
||||
<label id="semantic-search-endpoint-status"/>
|
||||
<button id="semantic-search-endpoint-configure"
|
||||
data-l10n-id="preferences-advanced-semantic-search-endpoint-configure"/>
|
||||
</hbox>
|
||||
|
||||
<vbox id="semantic-search-status" hidden="true">
|
||||
<separator class="thin"/>
|
||||
<hbox align="center">
|
||||
<label data-l10n-id="preferences-advanced-semantic-search-status"/>
|
||||
<label id="semantic-search-phase"/>
|
||||
|
|
@ -350,11 +340,6 @@
|
|||
data-l10n-id="preferences-advanced-semantic-search-stop"
|
||||
hidden="true"/>
|
||||
</hbox>
|
||||
<hbox id="semantic-search-endpoint-row" align="center" hidden="true">
|
||||
<label id="semantic-search-endpoint-status"/>
|
||||
<button id="semantic-search-endpoint-configure"
|
||||
data-l10n-id="preferences-advanced-semantic-search-endpoint-configure"/>
|
||||
</hbox>
|
||||
<html:div id="semantic-search-progress">
|
||||
<html:div id="semantic-search-items-row" class="semantic-search-progress-row">
|
||||
<label data-l10n-id="preferences-advanced-semantic-search-items"/>
|
||||
|
|
@ -367,7 +352,30 @@
|
|||
<label id="semantic-search-attachments-value"/>
|
||||
</html:div>
|
||||
</html:div>
|
||||
<html:div id="semantic-search-diagnostics"/>
|
||||
<button id="semantic-search-diagnostics-toggle"
|
||||
data-l10n-id="preferences-advanced-semantic-search-diagnostics-show"/>
|
||||
<html:div id="semantic-search-diagnostics" hidden="true"/>
|
||||
</vbox>
|
||||
|
||||
<!-- Temporary, for testing: pin best-match search to one engine -->
|
||||
<vbox>
|
||||
<separator class="thin"/>
|
||||
<hbox align="center">
|
||||
<label data-l10n-id="preferences-advanced-best-match-engine"
|
||||
control="best-match-engine"/>
|
||||
<menulist id="best-match-engine"
|
||||
preference="extensions.zotero.search.bestMatchEngine"
|
||||
native="true">
|
||||
<menupopup>
|
||||
<menuitem value="hybrid"
|
||||
data-l10n-id="preferences-advanced-best-match-engine-hybrid"/>
|
||||
<menuitem value="lexical"
|
||||
data-l10n-id="preferences-advanced-best-match-engine-lexical"/>
|
||||
<menuitem value="semantic"
|
||||
data-l10n-id="preferences-advanced-best-match-engine-semantic"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</groupbox>
|
||||
</vbox>
|
||||
|
|
|
|||
|
|
@ -3926,7 +3926,8 @@ Zotero.Embeddings.Endpoint = new function () {
|
|||
this.getStatus = function () {
|
||||
let url = Zotero.Prefs.get('embeddings.endpoint') || '';
|
||||
let status = { url, state: 'off', serverModel: null };
|
||||
if (!url) {
|
||||
// A verdict is about a model, so there is none to report without one
|
||||
if (!url || !Zotero.Embeddings.isEnabled()) {
|
||||
return status;
|
||||
}
|
||||
if (_verdict === undefined) {
|
||||
|
|
|
|||
|
|
@ -102,7 +102,6 @@ preferences-advanced-semantic-search-chinese =
|
|||
.label = Chinese
|
||||
preferences-advanced-semantic-search-multilingual =
|
||||
.label = Multilingual
|
||||
preferences-advanced-semantic-search-model-description = “{ preferences-advanced-semantic-search-english.label }” and “{ preferences-advanced-semantic-search-chinese.label }” give the best results for libraries in those languages. “{ preferences-advanced-semantic-search-multilingual.label }” supports searching in and across many languages.
|
||||
preferences-advanced-semantic-search-index-fulltext =
|
||||
.label = Search full text of attachment files
|
||||
preferences-advanced-best-match-engine = Engine (temporary, for testing):
|
||||
|
|
@ -112,7 +111,8 @@ preferences-advanced-best-match-engine-lexical =
|
|||
.label = Lexical
|
||||
preferences-advanced-best-match-engine-semantic =
|
||||
.label = Semantic
|
||||
preferences-advanced-best-match-margin = Drop results scoring more than this far below the best result (%):
|
||||
preferences-advanced-best-match-margin = Quality cutoff below top (%):
|
||||
preferences-advanced-best-match-margin-description = At 30%, results scoring up to 30% below the best one are kept; 100% keeps every result, 0 drops everything but the top match.
|
||||
preferences-advanced-semantic-search-downloading = Downloading…
|
||||
preferences-advanced-semantic-search-downloading-progress = Downloading… { $percent }%
|
||||
preferences-advanced-semantic-search-extracting = Preparing documents…
|
||||
|
|
@ -127,9 +127,13 @@ preferences-advanced-semantic-search-resume =
|
|||
.label = Resume
|
||||
preferences-advanced-semantic-search-stop =
|
||||
.label = Stop
|
||||
preferences-advanced-semantic-search-items = Items and notes
|
||||
preferences-advanced-semantic-search-items = Metadata, notes, annotations
|
||||
preferences-advanced-semantic-search-attachments = Attachments
|
||||
preferences-advanced-semantic-search-progress-value = { $percent }% ({ $done } / { $total })
|
||||
preferences-advanced-semantic-search-progress-value = { $percent }%
|
||||
preferences-advanced-semantic-search-diagnostics-show =
|
||||
.label = Show Diagnostics
|
||||
preferences-advanced-semantic-search-diagnostics-hide =
|
||||
.label = Hide Diagnostics
|
||||
preferences-advanced-semantic-search-switch-title = Change Mode?
|
||||
preferences-advanced-semantic-search-switch-text = Changing the mode will rebuild the search index, which can take a long time for large libraries.
|
||||
preferences-advanced-semantic-search-switch-button = Change Mode
|
||||
|
|
|
|||
|
|
@ -98,40 +98,78 @@
|
|||
margin-inline-start: 6px;
|
||||
}
|
||||
|
||||
#semantic-search-progress {
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr max-content;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
row-gap: 4px;
|
||||
margin-top: 4px;
|
||||
#semantic-search {
|
||||
// One gap between rows; the controls' own block margins would make
|
||||
// the gap depend on which control a row holds
|
||||
row-gap: 6px;
|
||||
|
||||
.semantic-search-progress-row {
|
||||
display: contents;
|
||||
> hbox > :is(menulist, button, .html-input),
|
||||
> vbox > hbox > :is(menulist, button) {
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
// Sized to its first item at construction, before the models are added
|
||||
#semantic-search-model {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
#best-match-margin {
|
||||
width: 5em;
|
||||
}
|
||||
|
||||
.semantic-search-hint {
|
||||
opacity: 0.7;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
#semantic-search-status {
|
||||
row-gap: 6px;
|
||||
}
|
||||
|
||||
#semantic-search-diagnostics-toggle {
|
||||
align-self: flex-start;
|
||||
margin-inline: 0;
|
||||
}
|
||||
|
||||
// Bars and figures share one label column
|
||||
#semantic-search-progress,
|
||||
#semantic-search-diagnostics {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
column-gap: 12px;
|
||||
row-gap: 4px;
|
||||
}
|
||||
|
||||
#semantic-search-progress {
|
||||
grid-template-columns: max-content 1fr max-content;
|
||||
|
||||
.semantic-search-progress-row {
|
||||
display: contents;
|
||||
|
||||
&[hidden] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
progress {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#semantic-search-diagnostics {
|
||||
grid-template-columns: max-content 1fr;
|
||||
|
||||
&[hidden] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
progress {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#semantic-search-diagnostics {
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr;
|
||||
column-gap: 12px;
|
||||
row-gap: 2px;
|
||||
margin-top: 10px;
|
||||
|
||||
label:nth-child(odd) {
|
||||
color: var(--fill-secondary);
|
||||
}
|
||||
|
||||
label:nth-child(even) {
|
||||
white-space: normal;
|
||||
|
||||
label:nth-child(odd) {
|
||||
color: var(--fill-secondary);
|
||||
}
|
||||
|
||||
label:nth-child(even) {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -148,18 +186,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
#semantic-search-model-description {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#semantic-search-libraries {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
#semantic-search-endpoint-row {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
#zotero-embeddings-endpoint-container {
|
||||
.endpoint-note {
|
||||
opacity: 0.7;
|
||||
|
|
|
|||
|
|
@ -1389,6 +1389,7 @@ describe("Zotero.Embeddings", function () {
|
|||
sinon.stub(Zotero.Embeddings, 'getModelVersion').returns('test-endpoint/1'),
|
||||
sinon.stub(Zotero.Embeddings, 'embedMany').callsFake(async texts => texts.map(text => vectorFor(text)))
|
||||
];
|
||||
await Zotero.Embeddings.initDB();
|
||||
await Zotero.DB.queryAsync("DELETE FROM embeddings.itemEmbeddingsMeta WHERE key='endpoint'");
|
||||
Zotero.Embeddings.Endpoint.reset();
|
||||
});
|
||||
|
|
@ -1399,6 +1400,14 @@ describe("Zotero.Embeddings", function () {
|
|||
await Zotero.DB.queryAsync("DELETE FROM embeddings.itemEmbeddingsMeta WHERE key='endpoint'");
|
||||
});
|
||||
|
||||
it("should report a configured endpoint as off while no model is selected", async function () {
|
||||
Zotero.Prefs.set('embeddings.endpoint', URL);
|
||||
Zotero.Embeddings.getModelName.returns('');
|
||||
Zotero.Embeddings.getModelVersion.throws(new Error("Unknown embeddings model ''"));
|
||||
assert.equal(Zotero.Embeddings.Endpoint.getStatus().state, 'off');
|
||||
assert.doesNotThrow(() => Zotero.Embeddings.Indexing.getStatus());
|
||||
});
|
||||
|
||||
it("should describe how to serve the model", function () {
|
||||
let { command, url } = Zotero.Embeddings.Endpoint.getCommand();
|
||||
assert.include(command, 'hotchpotch/bekko-embedding-v1-a8m-GGUF:F16');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue