dsh-molbio-panel 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +198 -1
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -8517,6 +8517,44 @@ window.__ModuleLoader__.load({
|
|
|
8517
8517
|
return undefined;
|
|
8518
8518
|
}
|
|
8519
8519
|
|
|
8520
|
+
/** The credential reference the PubMed tools read first. */
|
|
8521
|
+
const NCBI_KEY_REF = 'NCBI_API_KEY';
|
|
8522
|
+
|
|
8523
|
+
/**
|
|
8524
|
+
* Whether a Plugins-page detail subject is this package's own entry.
|
|
8525
|
+
*
|
|
8526
|
+
* Bundles and rows both expose `pkg.name`; the key section is offered on the
|
|
8527
|
+
* bundle page and on every row page the bundle declares, and on nothing else.
|
|
8528
|
+
*/
|
|
8529
|
+
function isMolbioDetailSubject(subject) {
|
|
8530
|
+
if (subject === null || typeof subject !== 'object') return false;
|
|
8531
|
+
if (subject.kind !== 'bundle' && subject.kind !== 'row') return false;
|
|
8532
|
+
const pkg = subject.pkg;
|
|
8533
|
+
return pkg !== null && typeof pkg === 'object' && pkg.name === 'dsh-molbio-tools';
|
|
8534
|
+
}
|
|
8535
|
+
|
|
8536
|
+
/**
|
|
8537
|
+
* The host failure message carried by a Remote result, or '' on success.
|
|
8538
|
+
* Remote calls answer `{ ok: true, value }` or `{ ok: false, error }`.
|
|
8539
|
+
*/
|
|
8540
|
+
function remoteFailure(response) {
|
|
8541
|
+
if (response === null || typeof response !== 'object' || response.ok !== false) return '';
|
|
8542
|
+
const message = response.error?.message;
|
|
8543
|
+
return typeof message === 'string' && message !== '' ? message : 'the host refused the request';
|
|
8544
|
+
}
|
|
8545
|
+
|
|
8546
|
+
/** One reference's view from a `credentials.describe` response (never the value). */
|
|
8547
|
+
function credentialInfo(response, ref) {
|
|
8548
|
+
const view = response !== null && typeof response === 'object' && response.ok !== false
|
|
8549
|
+
? response.value?.[ref]
|
|
8550
|
+
: undefined;
|
|
8551
|
+
return {
|
|
8552
|
+
configured: view?.configured === true,
|
|
8553
|
+
writable: view?.writable !== false,
|
|
8554
|
+
...typeof view?.source === 'string' && view.source !== '' ? { source: view.source } : {},
|
|
8555
|
+
};
|
|
8556
|
+
}
|
|
8557
|
+
|
|
8520
8558
|
exports.PLASMID_EXTENSIONS = PLASMID_EXTENSIONS;
|
|
8521
8559
|
exports.ALIGNMENT_EXTENSIONS = ALIGNMENT_EXTENSIONS;
|
|
8522
8560
|
exports.extensionOf = extensionOf;
|
|
@@ -8541,6 +8579,10 @@ window.__ModuleLoader__.load({
|
|
|
8541
8579
|
exports.paperFields = paperFields;
|
|
8542
8580
|
exports.paperSummary = paperSummary;
|
|
8543
8581
|
exports.paperLink = paperLink;
|
|
8582
|
+
exports.NCBI_KEY_REF = NCBI_KEY_REF;
|
|
8583
|
+
exports.isMolbioDetailSubject = isMolbioDetailSubject;
|
|
8584
|
+
exports.remoteFailure = remoteFailure;
|
|
8585
|
+
exports.credentialInfo = credentialInfo;
|
|
8544
8586
|
return exports;
|
|
8545
8587
|
};
|
|
8546
8588
|
__molbio_modules["build/client-entry.mjs"] = () => {
|
|
@@ -8577,7 +8619,7 @@ window.__ModuleLoader__.load({
|
|
|
8577
8619
|
* inside the factory would also have to be torn down by hand on unload.
|
|
8578
8620
|
*/
|
|
8579
8621
|
const { createElement: h, useEffect, useMemo, useRef, useState } = __ext0;
|
|
8580
|
-
const { LIBRARY_FILE, childPath, classifyEntry, decodeWorkspaceBytes, filterPapers, isOpenable, libraryTags, logoSvg, mapCardSummary, mapCardView, paperFields, paperLink, paperSummary, parseAlignmentFile, parseLibrary, parsePlasmidFile, plasmidSvg, readWorkspaceText, sortEntries } = __molbio_require("build/panel-core.mjs");
|
|
8622
|
+
const { LIBRARY_FILE, NCBI_KEY_REF, childPath, classifyEntry, credentialInfo, isMolbioDetailSubject, remoteFailure, decodeWorkspaceBytes, filterPapers, isOpenable, libraryTags, logoSvg, mapCardSummary, mapCardView, paperFields, paperLink, paperSummary, parseAlignmentFile, parseLibrary, parsePlasmidFile, plasmidSvg, readWorkspaceText, sortEntries } = __molbio_require("build/panel-core.mjs");
|
|
8581
8623
|
/** This implementation's identity, and the key its body/title register under. */
|
|
8582
8624
|
const PANEL_ID = 'dsh-molbio-tools';
|
|
8583
8625
|
/** Type discriminator the tab is opened by. */
|
|
@@ -9015,6 +9057,142 @@ window.__ModuleLoader__.load({
|
|
|
9015
9057
|
h('div', { style: styles.main }, ...detail));
|
|
9016
9058
|
}
|
|
9017
9059
|
|
|
9060
|
+
/** The state of the PubMed key, read from the host's credential store. */
|
|
9061
|
+
async function readNcbiKey(remote) {
|
|
9062
|
+
if (remote?.credentials === undefined || typeof remote.credentials.describe !== 'function') {
|
|
9063
|
+
return { phase: 'error', configured: false, writable: false, message: 'This deployment exposes no credentials Remote (the settings controller is not mounted).' };
|
|
9064
|
+
}
|
|
9065
|
+
let response;
|
|
9066
|
+
try {
|
|
9067
|
+
response = await remote.credentials.describe([NCBI_KEY_REF]);
|
|
9068
|
+
} catch (error) {
|
|
9069
|
+
response = { ok: false, error: { message: error instanceof Error ? error.message : String(error) } };
|
|
9070
|
+
}
|
|
9071
|
+
const failure = remoteFailure(response);
|
|
9072
|
+
if (failure !== '') return { phase: 'error', configured: false, writable: false, message: failure };
|
|
9073
|
+
return { phase: 'ready', ...credentialInfo(response, NCBI_KEY_REF), message: '' };
|
|
9074
|
+
}
|
|
9075
|
+
|
|
9076
|
+
const keyStyles = {
|
|
9077
|
+
section: { borderTop: '1px solid rgba(127, 127, 127, 0.28)', marginTop: 16, paddingTop: 12, display: 'grid', gap: 8 },
|
|
9078
|
+
title: { margin: 0, fontSize: 14 },
|
|
9079
|
+
note: { margin: 0, fontSize: 12, opacity: 0.75, lineHeight: 1.5 },
|
|
9080
|
+
status: { margin: 0, fontSize: 12 },
|
|
9081
|
+
row: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' },
|
|
9082
|
+
input: { flex: '1 1 240px', minWidth: 200, padding: '4px 8px' },
|
|
9083
|
+
message: { margin: 0, fontSize: 12 },
|
|
9084
|
+
};
|
|
9085
|
+
|
|
9086
|
+
/**
|
|
9087
|
+
* The Plugins → installed plugin → detail section that stores the NCBI key.
|
|
9088
|
+
*
|
|
9089
|
+
* It renders only for this package's own subject (bundle page, or one of the
|
|
9090
|
+
* row pages the bundle declares) and returns null everywhere else, which is the
|
|
9091
|
+
* contract `plugins.detail.section` asks a contribution to honour. The value is
|
|
9092
|
+
* write-only from here: `credentials.describe` answers whether a key is
|
|
9093
|
+
* configured and where it came from, never the key itself.
|
|
9094
|
+
*/
|
|
9095
|
+
function NcbiKeySection({ subject, remote }) {
|
|
9096
|
+
const [draft, setDraft] = useState('');
|
|
9097
|
+
const [state, setState] = useState({ phase: 'loading', configured: false, writable: true, message: '' });
|
|
9098
|
+
const subjectToken = isMolbioDetailSubject(subject)
|
|
9099
|
+
? `${subject.kind}:${subject.pkg.name}:${subject.row?.rowId ?? ''}`
|
|
9100
|
+
: '';
|
|
9101
|
+
useEffect(() => {
|
|
9102
|
+
if (subjectToken === '') return undefined;
|
|
9103
|
+
let live = true;
|
|
9104
|
+
const load = () => readNcbiKey(remote).then((next) => { if (live) setState(next); });
|
|
9105
|
+
load();
|
|
9106
|
+
// Another surface may rotate or clear the same reference (Settings, the
|
|
9107
|
+
// file watcher); follow the host event instead of showing a stale badge.
|
|
9108
|
+
const off = typeof remote?.$on === 'function'
|
|
9109
|
+
? remote.$on('credentials/reference-updated', (ref) => { if (ref === NCBI_KEY_REF) load(); })
|
|
9110
|
+
: undefined;
|
|
9111
|
+
return () => {
|
|
9112
|
+
live = false;
|
|
9113
|
+
if (typeof off === 'function') off();
|
|
9114
|
+
};
|
|
9115
|
+
}, [remote, subjectToken]);
|
|
9116
|
+
if (!isMolbioDetailSubject(subject)) return null;
|
|
9117
|
+
const busy = state.phase === 'saving';
|
|
9118
|
+
const save = async () => {
|
|
9119
|
+
const value = draft.trim();
|
|
9120
|
+
if (value === '') return;
|
|
9121
|
+
if (remote?.credentials === undefined || typeof remote.credentials.set !== 'function') {
|
|
9122
|
+
setState((previous) => ({ ...previous, phase: 'error', message: 'This deployment exposes no credentials Remote (the settings controller is not mounted).' }));
|
|
9123
|
+
return;
|
|
9124
|
+
}
|
|
9125
|
+
setState((previous) => ({ ...previous, phase: 'saving', message: '' }));
|
|
9126
|
+
let response;
|
|
9127
|
+
try {
|
|
9128
|
+
response = await remote.credentials.set(NCBI_KEY_REF, value);
|
|
9129
|
+
} catch (error) {
|
|
9130
|
+
response = { ok: false, error: { message: error instanceof Error ? error.message : String(error) } };
|
|
9131
|
+
}
|
|
9132
|
+
const failure = remoteFailure(response);
|
|
9133
|
+
setDraft('');
|
|
9134
|
+
const next = await readNcbiKey(remote);
|
|
9135
|
+
setState(failure !== ''
|
|
9136
|
+
? { ...next, phase: 'error', message: failure }
|
|
9137
|
+
: next.phase === 'error'
|
|
9138
|
+
? next
|
|
9139
|
+
: next.configured === true
|
|
9140
|
+
? { ...next, message: 'Saved. The next PubMed call uses this key.' }
|
|
9141
|
+
: { ...next, phase: 'error', message: 'The host accepted the write but still reports no key configured. Restart the profile and try again.' });
|
|
9142
|
+
};
|
|
9143
|
+
const remove = async () => {
|
|
9144
|
+
if (remote?.credentials === undefined || typeof remote.credentials.unset !== 'function') {
|
|
9145
|
+
setState((previous) => ({ ...previous, phase: 'error', message: 'This deployment exposes no credentials Remote (the settings controller is not mounted).' }));
|
|
9146
|
+
return;
|
|
9147
|
+
}
|
|
9148
|
+
setState((previous) => ({ ...previous, phase: 'saving', message: '' }));
|
|
9149
|
+
let response;
|
|
9150
|
+
try {
|
|
9151
|
+
response = await remote.credentials.unset(NCBI_KEY_REF);
|
|
9152
|
+
} catch (error) {
|
|
9153
|
+
response = { ok: false, error: { message: error instanceof Error ? error.message : String(error) } };
|
|
9154
|
+
}
|
|
9155
|
+
const failure = remoteFailure(response);
|
|
9156
|
+
const next = await readNcbiKey(remote);
|
|
9157
|
+
setState(failure !== ''
|
|
9158
|
+
? { ...next, phase: 'error', message: failure }
|
|
9159
|
+
: next.phase === 'error'
|
|
9160
|
+
? next
|
|
9161
|
+
: next.configured === false
|
|
9162
|
+
? { ...next, message: 'Removed. PubMed search falls back to web search.' }
|
|
9163
|
+
: { ...next, phase: 'error', message: 'The host still reports a key configured after Remove.' });
|
|
9164
|
+
};
|
|
9165
|
+
const status = state.phase === 'loading'
|
|
9166
|
+
? 'Checking the credential store…'
|
|
9167
|
+
: state.phase === 'error'
|
|
9168
|
+
? 'Unavailable'
|
|
9169
|
+
: state.configured
|
|
9170
|
+
? `Configured${state.source === undefined ? '' : ` (source: ${state.source})`}`
|
|
9171
|
+
: 'Not configured';
|
|
9172
|
+
return h('section', { 'data-molbio-ncbi-key': '', style: keyStyles.section },
|
|
9173
|
+
h('h4', { style: keyStyles.title }, 'PubMed API key'),
|
|
9174
|
+
h('p', { style: keyStyles.note },
|
|
9175
|
+
'Stored in the harness credential store. molbio_pubmed_search and molbio_pubmed_abstract use it for the official PubMed E-utilities API; without it they fall back to web search.'),
|
|
9176
|
+
h('p', { 'data-molbio-key-status': state.phase, style: keyStyles.status }, status),
|
|
9177
|
+
h('div', { style: keyStyles.row },
|
|
9178
|
+
h('input', {
|
|
9179
|
+
type: 'password',
|
|
9180
|
+
value: draft,
|
|
9181
|
+
placeholder: 'NCBI API key (36 characters)',
|
|
9182
|
+
autoComplete: 'off',
|
|
9183
|
+
spellCheck: false,
|
|
9184
|
+
'aria-label': 'NCBI API key',
|
|
9185
|
+
style: keyStyles.input,
|
|
9186
|
+
onChange: (event) => setDraft(event.target.value),
|
|
9187
|
+
}),
|
|
9188
|
+
h('button', { type: 'button', disabled: busy || state.writable === false || draft.trim() === '', onClick: save },
|
|
9189
|
+
busy ? 'Saving…' : 'Save'),
|
|
9190
|
+
h('button', { type: 'button', disabled: busy || state.writable === false || state.configured !== true, onClick: remove }, 'Remove')),
|
|
9191
|
+
state.message === '' ? null : h('p', { 'data-molbio-key-message': '', role: 'status', style: keyStyles.message }, state.message),
|
|
9192
|
+
h('p', { style: keyStyles.note },
|
|
9193
|
+
'Create a key under NCBI → Account Settings → API Key Management. Prefer this over an environment variable: the store is not inherited by shell commands.'));
|
|
9194
|
+
}
|
|
9195
|
+
|
|
9018
9196
|
/** Required browser services. */
|
|
9019
9197
|
const inject = ['slots', 'sidebarRightTabs', 'remote'];
|
|
9020
9198
|
|
|
@@ -9098,6 +9276,25 @@ window.__ModuleLoader__.load({
|
|
|
9098
9276
|
key: toolName,
|
|
9099
9277
|
}, MolbioMapCard)), `molbio panel: ${toolName} card`);
|
|
9100
9278
|
}
|
|
9279
|
+
// Plugins -> the installed plugin's detail page: the one place a user can
|
|
9280
|
+
// store the NCBI key without editing a YAML file or exporting a variable.
|
|
9281
|
+
//
|
|
9282
|
+
// `remote.credentials` is a FIBER INJECTION, not merely a property on the
|
|
9283
|
+
// Remote carrier: the shipped settings pages declare it in `inject` for the
|
|
9284
|
+
// same reason. Without the declaration `ctx.remote.credentials` is undefined,
|
|
9285
|
+
// which is exactly how the first build of this section made Save a no-op.
|
|
9286
|
+
// The gate keeps the panel working in deployments that expose no credentials
|
|
9287
|
+
// namespace: the section simply never registers there.
|
|
9288
|
+
ctx.effect(() => {
|
|
9289
|
+
const gate = ctx.inject(['remote.credentials'], (keyCtx) => {
|
|
9290
|
+
keyCtx.effect(() => keyCtx.slots.inject('plugins.detail.section', () => keyCtx.slots.register({
|
|
9291
|
+
name: 'plugins.detail.section',
|
|
9292
|
+
id: 'molbio-ncbi-key',
|
|
9293
|
+
order: 100,
|
|
9294
|
+
}, (props) => h(NcbiKeySection, { ...props, remote: keyCtx.remote }))), 'molbio panel: NCBI key section');
|
|
9295
|
+
});
|
|
9296
|
+
return () => { gate.dispose(); };
|
|
9297
|
+
}, 'molbio panel: NCBI key section gate');
|
|
9101
9298
|
}
|
|
9102
9299
|
|
|
9103
9300
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-molbio-panel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Browser panel for the DeepSeek Harness Web GUI: a Molbio tab in the right sidebar that draws plasmid maps and sequence logos from workspace sequence files, using the same computation modules as the dsh-molbio-tools plugin.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.mjs",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"dshReleases": {
|
|
26
26
|
"0.2.1-rc.1": "unknown",
|
|
27
27
|
"0.2.1-rc.2": "compatible",
|
|
28
|
-
"0.2.1-rc.3": "
|
|
28
|
+
"0.2.1-rc.3": "compatible"
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"bundle": {
|