Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 46s
Forgejo Docker Build / Root app tests (push) Successful in 47s
Forgejo Android APK / Build signed APK (push) Successful in 1m56s
Forgejo Docker Build / Build Docker image (push) Successful in 18s
Forgejo Docker Build / Deploy to the host (push) Failing after 0s
PubMed joins web search as an optional source for a generated resource: a
literature search on the topic, with abstracts, cited by PMID in References.
Off by default, admin-enabled, with its own optional API key (NCBI raises the
rate limit from 3/sec to 10/sec; it works without one).
Neither search is a tool any more, and that is the point. Offering them as
function calls meant the model decided whether to search, and with a prompt
ending "Output ONLY Pandoc markdown" it decided not to — every time, with and
without corpus grounding, no matter how the tool description was worded.
Calling callAI with the tool directly produced a correct pubmed_search call, so
the plumbing was never the problem. The search only ever needed the topic, and
the route knows the topic before it calls the model, so both searches now run up
front and their results go into the prompt as findings, exactly the way corpus
excerpts do. Ticking the box now means the search happened.
Verified live against deepseek-v4-flash: 30 corpus excerpts and 6 PubMed
results, and a References slide carrying both the library sources and four real
PMIDs (29562151, 38506440, 35721052, 28814254).
Three fixes to illustration, which had never once fired:
- The dispatch call had been lost in a refactor. The tool was still offered, the
model still called it, and the call was dropped, so no job was ever enqueued.
- imageContext was passed as a bare topic string where dispatch expects
{ request, history }, which made the bound request undefined.
- The prompt never mentioned the tool existed while explicitly demanding only
markdown — the same suppression that killed the searches. It now says an
illustration is available and that calling it is not a violation of that rule.
my_resources is its own image workflow rather than a reuse of learning_hub,
because generated_image_links only accepts learning_hub assets, and that is
exactly the barrier that keeps a private illustration out of published content.
The illustration renders in the panel, rather than a toast pointing at an image
history this feature does not have.
Verified end to end: job queued, rendered, and the asset served to its owner as
a correctly labelled subglottic-anatomy teaching diagram.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
683 lines
48 KiB
HTML
683 lines
48 KiB
HTML
<div class="module-header">
|
|
<h2><i class="fas fa-user-shield"></i> Admin Panel</h2>
|
|
<p>Manage users, app settings, and view usage statistics</p>
|
|
</div>
|
|
|
|
<!-- Stats -->
|
|
<div class="admin-stats" id="admin-stats">
|
|
<div class="stat-card"><div class="stat-value" id="stat-users">—</div><div class="stat-label">Total Users</div></div>
|
|
<div class="stat-card"><div class="stat-value" id="stat-api-total">—</div><div class="stat-label">Total API Calls</div></div>
|
|
<div class="stat-card"><div class="stat-value" id="stat-api-today">—</div><div class="stat-label">API Calls Today</div></div>
|
|
</div>
|
|
|
|
<!-- Registration ─────────────────────────────────────────────
|
|
One card, read top to bottom: open registration at all, then decide
|
|
whether it needs an invitation, then hand out the codes. These were
|
|
two cards far apart, which made "enabled but invite-only" look like
|
|
two unrelated settings instead of one decision. -->
|
|
<div class="card">
|
|
<div class="card-header"><h3><i class="fas fa-door-open"></i> Registration</h3></div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:14px;">
|
|
<div style="display:flex;align-items:center;gap:16px;flex-wrap:wrap;">
|
|
<span id="reg-status-text" style="font-size:13px;color:var(--g600);">Loading...</span>
|
|
<button id="btn-toggle-reg" class="btn-sm btn-primary">Toggle</button>
|
|
</div>
|
|
|
|
<div class="admin-row">
|
|
<strong class="admin-row-label">Invite only</strong>
|
|
<div style="flex:1;display:flex;flex-direction:column;gap:4px;min-width:0;">
|
|
<label style="display:flex;align-items:center;gap:8px;font-size:13px;">
|
|
<input type="checkbox" id="admin-invite-only">
|
|
Require an invitation code to register
|
|
</label>
|
|
<p style="margin:0;font-size:12px;color:var(--g500);">Sits between open and closed registration. With registration disabled entirely, nobody can register even with a code.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="border-top:1px solid var(--g100);padding-top:12px;">
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;margin-bottom:8px;">Create an invitation</label>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
|
<input type="text" id="admin-invite-note" placeholder="Who is this for? (optional)" style="font-size:13px;padding:6px 10px;border:1px solid var(--g300);border-radius:6px;flex:1;min-width:180px;">
|
|
<label style="font-size:12px;color:var(--g600);display:flex;align-items:center;gap:6px;">Valid for
|
|
<input type="number" id="admin-invite-days" value="7" min="1" max="90" style="width:70px;font-size:13px;padding:6px 8px;border:1px solid var(--g300);border-radius:6px;"> days
|
|
</label>
|
|
<button id="btn-create-invite" class="btn-sm btn-primary" type="button"><i class="fas fa-plus"></i> Create</button>
|
|
</div>
|
|
<div id="admin-invite-new" style="margin-top:10px;"></div>
|
|
<p style="font-size:12px;color:var(--g500);margin:6px 0 0;">The code is shown once, here. Only its hash is stored, so it cannot be read again afterwards.</p>
|
|
</div>
|
|
|
|
<div id="admin-invites-list" style="display:flex;flex-direction:column;gap:4px;max-height:340px;overflow-y:auto;"></div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Users -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-users"></i> Users</h3>
|
|
<input id="admin-users-search" type="search" placeholder="Search name or email" style="flex:1;max-width:260px;font-size:13px;padding:5px 10px;border:1px solid var(--g300);border-radius:6px;" aria-label="Search users">
|
|
<button id="btn-refresh-users" class="btn-sm btn-ghost"><i class="fas fa-rotate"></i> Refresh</button>
|
|
</div>
|
|
<div style="overflow:auto;max-height:420px;border-top:1px solid var(--g100);">
|
|
<table class="admin-table" id="admin-users-table">
|
|
<thead style="position:sticky;top:0;background:white;z-index:1;">
|
|
<tr>
|
|
<th>Name / Email</th>
|
|
<th>Role</th>
|
|
<th>Status</th>
|
|
<th>Joined</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="admin-users-body">
|
|
<tr><td colspan="5" style="text-align:center;color:var(--g400);padding:20px;">Loading...</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── CMS: Announcement Banner ──────────────────────────────── -->
|
|
<div class="card">
|
|
<div class="card-header"><h3><i class="fas fa-bullhorn"></i> Announcement Banner</h3></div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:12px;">
|
|
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
|
|
<label style="font-size:13px;font-weight:600;">Show banner:</label>
|
|
<select id="cms-ann-enabled" style="font-size:13px;padding:4px 8px;border:1px solid var(--g300);border-radius:6px;">
|
|
<option value="false">Hidden</option>
|
|
<option value="true">Visible</option>
|
|
</select>
|
|
<select id="cms-ann-type" style="font-size:13px;padding:4px 8px;border:1px solid var(--g300);border-radius:6px;">
|
|
<option value="info">Info (blue)</option>
|
|
<option value="warning">Warning (amber)</option>
|
|
<option value="error">Error (red)</option>
|
|
<option value="success">Success (green)</option>
|
|
</select>
|
|
</div>
|
|
<textarea id="cms-ann-text" rows="2" style="font-size:13px;padding:8px;border:1px solid var(--g300);border-radius:6px;resize:vertical;" placeholder="Announcement message..."></textarea>
|
|
<div>
|
|
<button id="btn-save-announcement" class="btn-sm btn-primary">Save & Publish</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── CMS: Feature Flags ─────────────────────────────────────── -->
|
|
<div class="card">
|
|
<div class="card-header"><h3><i class="fas fa-toggle-on"></i> Feature Flags</h3></div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:10px;" id="cms-feature-flags">
|
|
<div style="display:flex;align-items:center;gap:12px;">
|
|
<label style="font-size:13px;font-weight:600;min-width:160px;">Read Aloud (TTS):</label>
|
|
<select id="cms-flag-read-aloud" style="font-size:13px;padding:4px 8px;border:1px solid var(--g300);border-radius:6px;">
|
|
<option value="true">Enabled</option>
|
|
<option value="false">Disabled</option>
|
|
</select>
|
|
</div>
|
|
<div style="display:flex;align-items:center;gap:12px;">
|
|
<label style="font-size:13px;font-weight:600;min-width:160px;">Nextcloud Integration:</label>
|
|
<select id="cms-flag-nextcloud" style="font-size:13px;padding:4px 8px;border:1px solid var(--g300);border-radius:6px;">
|
|
<option value="true">Enabled</option>
|
|
<option value="false">Disabled</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<button id="btn-save-flags" class="btn-sm btn-primary"><i class="fas fa-floppy-disk"></i> Save feature flags</button>
|
|
<span id="cms-flags-status" role="status" style="font-size:12px;color:var(--g500);margin-left:8px;"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── OIDC / SSO Configuration ──────────────────────────────── -->
|
|
<div class="card">
|
|
<div class="card-header"><h3><i class="fas fa-shield-halved"></i> Single Sign-On (OIDC)</h3></div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:12px;">
|
|
<p style="font-size:12px;color:var(--g500);margin:0;">Configure OpenID Connect for SSO with Azure AD, Okta, Keycloak, PocketID, Google, etc.<br>Callback URL: <code style="font-size:11px;background:var(--g100);padding:2px 6px;border-radius:4px;" id="oidc-callback-url"></code></p>
|
|
<div class="admin-row">
|
|
<label class="admin-row-label">Enable SSO:</label>
|
|
<select id="oidc-enabled" class="admin-control">
|
|
<option value="false">Disabled</option>
|
|
<option value="true">Enabled</option>
|
|
</select>
|
|
</div>
|
|
<div class="admin-row">
|
|
<label class="admin-row-label">Issuer URL:</label>
|
|
<input type="url" id="oidc-issuer" placeholder="https://id.example.com" class="admin-control">
|
|
</div>
|
|
<div class="admin-row">
|
|
<label class="admin-row-label">Client ID:</label>
|
|
<input type="text" id="oidc-client-id" placeholder="your-client-id" class="admin-control">
|
|
</div>
|
|
<div class="admin-row">
|
|
<label class="admin-row-label">Client Secret:</label>
|
|
<input type="password" id="oidc-client-secret" placeholder="Leave blank to keep current" class="admin-control">
|
|
</div>
|
|
<div class="admin-row">
|
|
<label class="admin-row-label">Button Label:</label>
|
|
<input type="text" id="oidc-button-label" placeholder="Sign in with SSO" class="admin-control">
|
|
</div>
|
|
<div class="admin-row">
|
|
<label class="admin-row-label">Disable local login:</label>
|
|
<select id="oidc-disable-local" class="admin-control">
|
|
<option value="false">No (both SSO and local login)</option>
|
|
<option value="true">Yes (SSO only)</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<button id="btn-save-oidc" class="btn-sm btn-primary"><i class="fas fa-floppy-disk"></i> Save OIDC Settings</button>
|
|
<span id="oidc-save-status" style="font-size:12px;margin-left:8px;color:var(--g500);"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── CMS: Email Templates ───────────────────────────────────── -->
|
|
<div class="card">
|
|
<div class="card-header"><h3><i class="fas fa-envelope"></i> Email Templates</h3></div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:16px;">
|
|
<!-- Email template selector -->
|
|
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
|
|
<label style="font-size:13px;font-weight:600;">Template:</label>
|
|
<select id="cms-email-template" style="font-size:13px;padding:4px 8px;border:1px solid var(--g300);border-radius:6px;">
|
|
<option value="verify">Email Verification</option>
|
|
<option value="reset">Password Reset</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);margin-bottom:4px;display:block;">Subject</label>
|
|
<input type="text" id="cms-email-subject" style="width:100%;font-size:13px;padding:8px;border:1px solid var(--g300);border-radius:6px;box-sizing:border-box;" placeholder="Email subject...">
|
|
</div>
|
|
<div>
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);margin-bottom:4px;display:block;">Body (plain text — a button link will be appended automatically)</label>
|
|
<textarea id="cms-email-body" rows="4" style="width:100%;font-size:13px;padding:8px;border:1px solid var(--g300);border-radius:6px;resize:vertical;box-sizing:border-box;" placeholder="Email body text..."></textarea>
|
|
</div>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap;">
|
|
<button id="btn-save-email" class="btn-sm btn-primary">Save Template</button>
|
|
<button id="btn-test-email" class="btn-sm btn-ghost"><i class="fas fa-paper-plane"></i> Send Test</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── CMS: AI Scribe Prompts ─────────────────────────────────── -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-robot"></i> AI Scribe</h3>
|
|
</div>
|
|
<div style="padding:16px;">
|
|
<div id="cms-scribe-prompts">Loading Scribe prompts...</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── CMS: SMTP Email Server ─────────────────────────────────── -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-server"></i> SMTP Email Server</h3>
|
|
<span id="smtp-source-badge" style="font-size:11px;padding:2px 8px;border-radius:10px;background:var(--g100);color:var(--g600);">Loading...</span>
|
|
</div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:12px;">
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
|
|
<div>
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;margin-bottom:4px;">SMTP Host</label>
|
|
<input type="text" id="cms-smtp-host" style="width:100%;font-size:13px;padding:7px;border:1px solid var(--g300);border-radius:6px;box-sizing:border-box;" placeholder="smtp.gmail.com">
|
|
</div>
|
|
<div>
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;margin-bottom:4px;">Port</label>
|
|
<input type="number" id="cms-smtp-port" style="width:100%;font-size:13px;padding:7px;border:1px solid var(--g300);border-radius:6px;box-sizing:border-box;" placeholder="587" value="587">
|
|
</div>
|
|
<div>
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;margin-bottom:4px;">Username / Email</label>
|
|
<input type="text" id="cms-smtp-user" style="width:100%;font-size:13px;padding:7px;border:1px solid var(--g300);border-radius:6px;box-sizing:border-box;" placeholder="user@example.com">
|
|
</div>
|
|
<div>
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;margin-bottom:4px;">Password / App Password</label>
|
|
<input type="password" id="cms-smtp-pass" style="width:100%;font-size:13px;padding:7px;border:1px solid var(--g300);border-radius:6px;box-sizing:border-box;" placeholder="Leave blank to keep current">
|
|
</div>
|
|
<div>
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;margin-bottom:4px;">From Address</label>
|
|
<input type="text" id="cms-smtp-from" style="width:100%;font-size:13px;padding:7px;border:1px solid var(--g300);border-radius:6px;box-sizing:border-box;" placeholder="Pediatric Scribe <noreply@example.com>">
|
|
</div>
|
|
<div style="display:flex;align-items:flex-end;gap:8px;">
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;">TLS/SSL (port 465):</label>
|
|
<select id="cms-smtp-secure" style="font-size:13px;padding:7px;border:1px solid var(--g300);border-radius:6px;">
|
|
<option value="false">STARTTLS (587)</option>
|
|
<option value="true">SSL/TLS (465)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap;">
|
|
<button id="btn-save-smtp" class="btn-sm btn-primary">Save SMTP</button>
|
|
<button id="btn-clear-smtp" class="btn-sm btn-ghost"><i class="fas fa-trash"></i> Clear DB (use env)</button>
|
|
</div>
|
|
<p style="font-size:12px;color:var(--g500);margin:0;"><i class="fas fa-info-circle"></i> Environment variables take precedence over DB settings. Storing password in DB is convenient but consider using env vars for production security.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── CMS: Saved Encounters (Admin View) ─────────────────────── -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-database"></i> Saved Encounters (Site-wide)</h3>
|
|
<span style="font-size:12px;color:var(--g500);">Auto-deleted after <span id="admin-auto-delete-days">7</span> days</span>
|
|
</div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:10px;">
|
|
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
|
|
<label style="font-size:13px;font-weight:600;">Auto-delete after:</label>
|
|
<select id="cms-auto-delete-days" style="font-size:13px;padding:4px 8px;border:1px solid var(--g300);border-radius:6px;">
|
|
<option value="1">1 day</option>
|
|
<option value="3">3 days</option>
|
|
<option value="7" selected>7 days</option>
|
|
<option value="14">14 days</option>
|
|
<option value="30">30 days</option>
|
|
</select>
|
|
<button id="btn-save-auto-delete" class="btn-sm btn-primary">Save</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── AI Model Management ─────────────────────────────────── -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-microchip"></i> AI Model Management</h3>
|
|
<span id="admin-model-provider-badge" style="font-size:11px;padding:2px 8px;border-radius:10px;background:var(--g100);color:var(--g600);">Loading...</span>
|
|
</div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:14px;">
|
|
|
|
<!-- Default Model -->
|
|
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
|
|
<label style="font-size:13px;font-weight:600;min-width:120px;">Default Model:</label>
|
|
<select id="admin-default-model" style="font-size:13px;padding:5px 8px;border:1px solid var(--g300);border-radius:6px;flex:1;max-width:400px;"></select>
|
|
<button id="btn-save-default-model" class="btn-sm btn-primary">Set Default</button>
|
|
</div>
|
|
|
|
<!-- Built-in Models Toggle -->
|
|
<div>
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;margin-bottom:8px;">Built-in Models (toggle to enable/disable for users)</label>
|
|
<div id="admin-builtin-models" style="display:flex;flex-direction:column;gap:4px;max-height:300px;overflow-y:auto;padding-right:4px;">
|
|
<p style="color:var(--g400);font-size:13px;">Loading...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Discover Models from API -->
|
|
<div style="border-top:1px solid var(--g100);padding-top:14px;">
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;margin-bottom:8px;">Discover Models from Provider API</label>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
|
<input type="text" id="admin-model-search" placeholder="Search models (e.g. gemini, gpt, llama)" style="font-size:13px;padding:6px 10px;border:1px solid var(--g300);border-radius:6px;flex:1;min-width:200px;">
|
|
<button id="btn-discover-models" class="btn-sm btn-primary"><i class="fas fa-magnifying-glass"></i> Search API</button>
|
|
</div>
|
|
<div id="admin-discovered-models" style="margin-top:10px;display:flex;flex-direction:column;gap:4px;max-height:400px;overflow-y:auto;">
|
|
</div>
|
|
<p id="admin-discover-hint" style="font-size:12px;color:var(--g500);margin:8px 0 0;">Click "Search API" to query your configured provider for all available models. Use the search box to filter results.</p>
|
|
</div>
|
|
|
|
|
|
<!-- Custom Models List -->
|
|
<div id="admin-custom-models-list" style="display:flex;flex-direction:column;gap:4px;">
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Clinical Assistant ─────────────────────────────────────── -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-brain"></i> Clinical Assistant / Learning</h3>
|
|
<span style="font-size:12px;color:var(--g500);">Model, retrieval and prompt settings</span>
|
|
</div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:12px;">
|
|
<p style="margin:0;padding:8px 10px;background:var(--g100);border-radius:6px;font-size:12px;color:var(--g600);">
|
|
Nothing on this page saves by itself. Each <strong>Save</strong> button applies only the
|
|
settings above it, up to the previous Save. This card has two: image settings have their
|
|
own, and everything else is saved by <strong>Save model & retrieval settings</strong>
|
|
at the bottom.
|
|
</p>
|
|
<div class="admin-row">
|
|
<label for="assistant-chat-model" class="admin-row-label">Chat model</label>
|
|
<select id="assistant-chat-model" class="admin-control"></select>
|
|
<button id="btn-test-assistant-chat-model" class="btn-sm btn-primary" type="button"><i class="fas fa-vial"></i> Test</button>
|
|
</div>
|
|
<div id="assistant-chat-test-result" style="font-size:12px;color:var(--g500);"></div>
|
|
<div class="admin-row" style="align-items:flex-start;">
|
|
<strong class="admin-row-label">Model availability</strong>
|
|
<div style="flex:1;display:flex;flex-direction:column;gap:8px;min-width:0;">
|
|
<p style="margin:0;font-size:12px;color:var(--g500);">Models users may select in the Clinical Assistant. Leave a list empty to keep only the configured model.</p>
|
|
<p style="margin:0;font-size:12px;color:var(--g500);">To make a new model appear here, add it from the gateway: chat models under <strong>AI Model Management</strong> above, image models under <strong>Image Generation</strong> below — search there, press <strong>+ Add</strong>, and it shows up in this list.</p>
|
|
<div>
|
|
<div style="font-size:12px;font-weight:600;color:var(--g600);margin-bottom:2px;">Chat models</div>
|
|
<div id="assistant-allowed-chat-models" aria-label="Chat models available to the Clinical Assistant"></div>
|
|
</div>
|
|
<div>
|
|
<div style="font-size:12px;font-weight:600;color:var(--g600);margin-bottom:2px;">Image models</div>
|
|
<div id="assistant-allowed-image-models" aria-label="Image models available to the Clinical Assistant"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div style="border:1px solid var(--g100);border-radius:8px;padding:10px;display:flex;gap:10px;align-items:center;justify-content:space-between;flex-wrap:wrap;">
|
|
<div>
|
|
<div style="font-size:13px;font-weight:600;color:var(--g700);">Starter prompt pool</div>
|
|
<div id="assistant-prompt-pool-status" style="font-size:12px;color:var(--g500);margin-top:3px;">Checking prompt pool...</div>
|
|
</div>
|
|
<button id="btn-regenerate-assistant-prompt-pool" class="btn-sm btn-ghost" type="button"><i class="fas fa-rotate"></i> Regenerate Pool</button>
|
|
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;width:100%;">
|
|
<select id="assistant-prompt-pool-snapshots" class="admin-control" style="flex:1;min-width:220px;"><option value="">Loading snapshots...</option></select>
|
|
<button id="btn-restore-assistant-prompt-pool" class="btn-sm btn-ghost" type="button"><i class="fas fa-clock-rotate-left"></i> Restore Snapshot</button>
|
|
</div>
|
|
</div>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
|
|
<div class="admin-row">
|
|
<label for="assistant-search-limit" class="admin-row-label">Retrieval result limit</label>
|
|
<input id="assistant-search-limit" type="number" min="3" max="20" value="8" class="admin-control">
|
|
</div>
|
|
<div class="admin-row">
|
|
<label for="assistant-context-chars" class="admin-row-label">Context per excerpt (characters)</label>
|
|
<input id="assistant-context-chars" type="number" min="300" max="4000" value="1400" class="admin-control">
|
|
</div>
|
|
</div>
|
|
<div class="admin-row">
|
|
<strong class="admin-row-label">Citations</strong>
|
|
<div style="flex:1;display:flex;flex-direction:column;gap:4px;min-width:0;">
|
|
<label style="display:flex;align-items:center;gap:8px;font-size:13px;">
|
|
<input type="checkbox" id="assistant-show-sources" checked>
|
|
Show the Sources panel and numbered citations
|
|
</label>
|
|
<p style="margin:0;font-size:12px;color:var(--g500);">Display only. The AI prompt, retrieval and the stored answer are byte-for-byte identical either way, so turning this off cannot change what the model says. Citations are hidden, not removed — turning it back on restores them.</p>
|
|
</div>
|
|
</div>
|
|
<div class="admin-row">
|
|
<label for="assistant-translate-provider" class="admin-row-label">Translation provider</label>
|
|
<select id="assistant-translate-provider" class="admin-control">
|
|
<option value="libretranslate">Local LibreTranslate (patient text stays on this network)</option>
|
|
</select>
|
|
</div>
|
|
<div class="admin-row">
|
|
<strong class="admin-row-label">Conversation input budget</strong>
|
|
<div style="flex:1;display:flex;flex-direction:column;gap:4px;min-width:0;">
|
|
<input id="assistant-conversation-budget" type="number" min="1000" max="1000000" class="admin-control" placeholder="e.g. 120000" aria-label="Conversation input budget (UTF-16 code units)">
|
|
<p id="assistant-conversation-budget-meta" role="status" style="margin:0;font-size:12px;color:var(--g500);">UTF-16 code units. Leave empty to use CLINICAL_ASSISTANT_CONVERSATION_CHARS.</p>
|
|
</div>
|
|
</div>
|
|
<section aria-labelledby="clinical-prompts-heading">
|
|
<h4 id="clinical-prompts-heading">Clinical Assistant prompts</h4>
|
|
<div id="cms-clinical-prompts">Loading clinical prompts...</div>
|
|
</section>
|
|
<section aria-labelledby="learning-prompts-heading">
|
|
<h4 id="learning-prompts-heading">Learning prompts</h4>
|
|
<div id="cms-learning-prompts">Loading Learning prompts...</div>
|
|
</section>
|
|
<section id="workflow-image-settings" aria-label="Workflow image settings"></section>
|
|
<div id="assistant-config-error" role="alert" hidden
|
|
style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:10px 12px;border:1px solid var(--red);border-radius:8px;background:rgba(220,38,38,0.06);">
|
|
<span style="font-size:13px;color:var(--g700);">
|
|
<i class="fas fa-triangle-exclamation" style="color:var(--red);"></i>
|
|
These settings could not be loaded, so saving is disabled. Nothing you have typed has been lost.
|
|
</span>
|
|
<button id="btn-retry-assistant-config" class="btn-sm btn-ghost" type="button">Try again</button>
|
|
</div>
|
|
<div style="border-top:1px solid var(--g100);padding-top:12px;display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
|
<button id="btn-save-assistant-config" class="btn-sm btn-primary" disabled><i class="fas fa-floppy-disk"></i> Save model & retrieval settings</button>
|
|
<span style="font-size:12px;color:var(--g500);">Applies everything in this card except the image settings above.</span>
|
|
<span id="assistant-admin-status" role="status" style="font-size:12px;color:var(--g500);"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Web search ─────────────────────────────────────────────── -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-globe"></i> Search Sources</h3>
|
|
<span style="font-size:12px;color:var(--g500);">Off by default</span>
|
|
</div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:14px;">
|
|
<p style="margin:0;padding:8px 10px;background:rgba(224,142,69,0.10);border:1px solid var(--orange, #e08e45);border-radius:6px;font-size:12px;color:var(--g700);">
|
|
<strong>This sends text outside the building.</strong> When a resource author turns
|
|
it on, the model may send a short search query to the provider below. Only the query
|
|
leaves — never library excerpts, never the generated content, never anything
|
|
about the user — but a query written while drafting clinical material can still
|
|
carry clinical detail, and the provider keeps its own logs. SearXNG is the only
|
|
option here that you host yourself.
|
|
</p>
|
|
|
|
<div class="admin-row">
|
|
<strong class="admin-row-label">Enabled</strong>
|
|
<div style="flex:1;min-width:0;">
|
|
<select id="ws-enabled" class="admin-control" style="max-width:200px;">
|
|
<option value="false">Disabled</option>
|
|
<option value="true">Enabled</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-row">
|
|
<label for="ws-provider" class="admin-row-label">Provider</label>
|
|
<div style="flex:1;min-width:0;">
|
|
<select id="ws-provider" class="admin-control" style="max-width:260px;">
|
|
<option value="tavily">Tavily — built for AI, clean extracts</option>
|
|
<option value="serper">Serper — Google results</option>
|
|
<option value="brave">Brave Search API</option>
|
|
<option value="searxng">SearXNG — self-hosted, no third party</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-row">
|
|
<label for="ws-api-key" class="admin-row-label">API key</label>
|
|
<div style="flex:1;display:flex;flex-direction:column;gap:4px;min-width:0;">
|
|
<input id="ws-api-key" type="password" class="admin-control" placeholder="Leave blank to keep the current key" autocomplete="off">
|
|
<p style="margin:0;font-size:12px;color:var(--g500);">Not needed for SearXNG. Shown masked once saved.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-row">
|
|
<label for="ws-base-url" class="admin-row-label">SearXNG URL</label>
|
|
<div style="flex:1;display:flex;flex-direction:column;gap:4px;min-width:0;">
|
|
<input id="ws-base-url" type="text" class="admin-control" placeholder="https://searxng.example.com">
|
|
<p style="margin:0;font-size:12px;color:var(--g500);">Only used when the provider is SearXNG. Its <code>/search?format=json</code> endpoint must be enabled.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="border-top:1px solid var(--g100);padding-top:12px;">
|
|
<div style="font-size:13px;font-weight:600;color:var(--g700);margin-bottom:8px;">
|
|
<i class="fas fa-book-medical"></i> PubMed
|
|
</div>
|
|
<p style="margin:0 0 10px;font-size:12px;color:var(--g500);">
|
|
A separate source from the web providers above, and a separate tick box for
|
|
authors. It returns structured records — title, journal, year, PMID —
|
|
so a reference can be exact rather than reconstructed from a page title. Queries
|
|
go to NCBI, not to a commercial provider.
|
|
</p>
|
|
|
|
<div class="admin-row">
|
|
<strong class="admin-row-label">Enabled</strong>
|
|
<div style="flex:1;min-width:0;">
|
|
<select id="pm-enabled" class="admin-control" style="max-width:200px;">
|
|
<option value="false">Disabled</option>
|
|
<option value="true">Enabled</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-row">
|
|
<label for="pm-api-key" class="admin-row-label">NCBI API key</label>
|
|
<div style="flex:1;display:flex;flex-direction:column;gap:4px;min-width:0;">
|
|
<input id="pm-api-key" type="password" class="admin-control" placeholder="Optional — leave blank to keep the current key" autocomplete="off">
|
|
<p style="margin:0;font-size:12px;color:var(--g500);">
|
|
Optional. PubMed works without one; a key raises the rate limit from 3
|
|
requests per second to 10, which matters for bulk work rather than for one
|
|
person writing a resource. Create one at
|
|
<a href="https://account.ncbi.nlm.nih.gov/settings/" target="_blank" rel="noopener noreferrer">account.ncbi.nlm.nih.gov/settings</a> — sign in, then <strong>API Key Management</strong> → <strong>Create an API Key</strong>.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-row">
|
|
<label for="pm-email" class="admin-row-label">Contact email</label>
|
|
<div style="flex:1;display:flex;flex-direction:column;gap:4px;min-width:0;">
|
|
<input id="pm-email" type="email" class="admin-control" placeholder="you@example.com">
|
|
<p style="margin:0;font-size:12px;color:var(--g500);">NCBI asks callers to identify themselves so they can contact you before blocking you. Optional but courteous.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="border-top:1px solid var(--g100);padding-top:12px;display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
|
<button id="btn-save-websearch" class="btn-sm btn-primary" type="button"><i class="fas fa-floppy-disk"></i> Save search settings</button>
|
|
<button id="btn-test-websearch" class="btn-sm btn-ghost" type="button"><i class="fas fa-vial"></i> Test</button>
|
|
<span id="ws-status" role="status" style="font-size:12px;color:var(--g500);"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Citation quality ───────────────────────────────────────── -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-quote-right"></i> Citation Quality</h3>
|
|
<span id="admin-citation-summary" style="font-size:11px;padding:2px 8px;border-radius:10px;background:var(--g100);color:var(--g600);">Loading...</span>
|
|
</div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:12px;">
|
|
<p style="margin:0;font-size:12px;color:var(--g500);">Answers where the assistant wrote a citation number that no returned source matched. Those markers are never turned into links, so a reader sees plain text — this is where they are recorded. Consistent entries usually mean retrieval is returning fewer sources than the answer assumes.</p>
|
|
<button id="btn-view-citation-audit" class="btn-sm btn-primary" type="button"><i class="fas fa-list"></i> View recent</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── TTS Model Management ───────────────────────────────────── -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-volume-up"></i> Text-to-Speech (TTS)</h3>
|
|
<span id="admin-tts-provider-badge" style="font-size:11px;padding:2px 8px;border-radius:10px;background:var(--g100);color:var(--g600);">Loading...</span>
|
|
</div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:14px;">
|
|
<div id="admin-tts-info" style="font-size:12px;color:var(--g500);">Loading...</div>
|
|
|
|
<!-- Search voices from provider -->
|
|
<div>
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;margin-bottom:8px;">Search Voices / Models from Provider</label>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
|
<input type="text" id="admin-tts-search" placeholder="Filter voices (e.g. Journey, Neural, alloy)" style="font-size:13px;padding:6px 10px;border:1px solid var(--g300);border-radius:6px;flex:1;min-width:180px;">
|
|
<button id="btn-discover-tts" class="btn-sm btn-primary"><i class="fas fa-magnifying-glass"></i> Search</button>
|
|
</div>
|
|
<div id="admin-tts-discovered" style="margin-top:8px;display:flex;flex-direction:column;gap:4px;max-height:300px;overflow-y:auto;"></div>
|
|
<p id="admin-tts-discover-hint" style="font-size:12px;color:var(--g500);margin:6px 0 0;">Click Search to query available voices from your configured TTS provider.</p>
|
|
</div>
|
|
|
|
<!-- Test TTS -->
|
|
<div style="border-top:1px solid var(--g100);padding-top:12px;">
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;margin-bottom:8px;">Test Synthesis</label>
|
|
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:8px;">
|
|
<label style="font-size:13px;font-weight:600;min-width:80px;">Voice:</label>
|
|
<select id="admin-tts-voice" style="font-size:13px;padding:5px 8px;border:1px solid var(--g300);border-radius:6px;flex:1;max-width:280px;"></select>
|
|
</div>
|
|
<textarea id="admin-tts-test-text" rows="2" style="width:100%;font-size:13px;padding:8px;border:1px solid var(--g300);border-radius:6px;resize:vertical;box-sizing:border-box;" placeholder="Text to synthesize...">Hello, this is a text to speech test for the Pediatric AI Scribe.</textarea>
|
|
<div style="margin-top:8px;display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
|
<button id="btn-test-tts" class="btn-sm btn-primary"><i class="fas fa-play"></i> Synthesize & Play</button>
|
|
<audio id="admin-tts-audio" controls style="display:none;height:32px;flex:1;min-width:180px;"></audio>
|
|
</div>
|
|
<p id="admin-tts-result" style="font-size:12px;color:var(--g500);margin:6px 0 0;"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── STT Model Management ───────────────────────────────────── -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-microphone"></i> Speech-to-Text (STT)</h3>
|
|
<span id="admin-stt-provider-badge" style="font-size:11px;padding:2px 8px;border-radius:10px;background:var(--g100);color:var(--g600);">Loading...</span>
|
|
</div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:14px;">
|
|
<div id="admin-stt-info" style="font-size:12px;color:var(--g500);">Loading...</div>
|
|
|
|
<!-- Search models from provider -->
|
|
<div>
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;margin-bottom:8px;">Search Models from Provider</label>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
|
<input type="text" id="admin-stt-search" placeholder="Filter models (e.g. gemini, whisper)" style="font-size:13px;padding:6px 10px;border:1px solid var(--g300);border-radius:6px;flex:1;min-width:180px;">
|
|
<button id="btn-discover-stt" class="btn-sm btn-primary"><i class="fas fa-magnifying-glass"></i> Search</button>
|
|
</div>
|
|
<div id="admin-stt-discovered" style="margin-top:8px;display:flex;flex-direction:column;gap:4px;max-height:300px;overflow-y:auto;"></div>
|
|
<p id="admin-stt-discover-hint" style="font-size:12px;color:var(--g500);margin:6px 0 0;">Click Search to query available STT models from your configured provider.</p>
|
|
</div>
|
|
|
|
<!-- Test STT -->
|
|
<div style="border-top:1px solid var(--g100);padding-top:12px;">
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;margin-bottom:8px;">Test STT — Record & Transcribe</label>
|
|
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
|
<button id="btn-stt-record" class="btn-sm btn-primary"><i class="fas fa-microphone"></i> Start Recording</button>
|
|
<span id="admin-stt-recording-status" style="font-size:12px;color:var(--g500);"></span>
|
|
</div>
|
|
<div id="admin-stt-result" style="margin-top:10px;padding:10px;background:var(--g50);border-radius:6px;font-size:13px;color:var(--g700);display:none;">
|
|
<strong>Transcription:</strong> <span id="admin-stt-text"></span>
|
|
<div id="admin-stt-meta" style="font-size:11px;color:var(--g400);margin-top:4px;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Image Model Management ─────────────────────────────────── -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-image"></i> Image Generation</h3>
|
|
<span id="admin-image-provider-badge" style="font-size:11px;padding:2px 8px;border-radius:10px;background:var(--g100);color:var(--g600);">Gateway</span>
|
|
</div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:14px;">
|
|
<div id="admin-image-info" style="font-size:12px;color:var(--g500);">Image models come from the LiteLLM gateway. Search to list them, and test one before assigning it to a workflow.</div>
|
|
|
|
<!-- Search image models from provider -->
|
|
<div>
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;margin-bottom:8px;">Search Models from Provider</label>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
|
<input type="text" id="admin-image-search" placeholder="Filter models (e.g. dall-e, imagen, flux)" style="font-size:13px;padding:6px 10px;border:1px solid var(--g300);border-radius:6px;flex:1;min-width:180px;">
|
|
<button id="btn-discover-image" class="btn-sm btn-primary" type="button"><i class="fas fa-magnifying-glass"></i> Search</button>
|
|
</div>
|
|
<div id="admin-image-discovered" style="margin-top:8px;display:flex;flex-direction:column;gap:4px;max-height:300px;overflow-y:auto;"></div>
|
|
<p id="admin-image-discover-hint" style="font-size:12px;color:var(--g500);margin:6px 0 0;">Click Search to list the image models your gateway advertises. <strong>+ Add</strong> puts a model in the Clinical Assistant's <strong>Image models</strong> list; tick it there to offer it to users in chat. Unlike a voice, an image model has no single default — each workflow picks its own under <strong>Clinical Assistant / Learning</strong>.</p>
|
|
</div>
|
|
|
|
<!-- Test image generation -->
|
|
<div style="border-top:1px solid var(--g100);padding-top:12px;">
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;margin-bottom:8px;">Test Generation</label>
|
|
<p style="font-size:12px;color:var(--g500);margin:0 0 8px;">Press Test on a model above, or type an id the gateway does not advertise.</p>
|
|
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
|
<input type="text" id="admin-image-test-model" placeholder="Model id to test" style="font-size:13px;padding:6px 10px;border:1px solid var(--g300);border-radius:6px;flex:1;min-width:180px;">
|
|
<button id="btn-test-image-model" class="btn-sm btn-primary" type="button"><i class="fas fa-vial"></i> Test</button>
|
|
</div>
|
|
<div id="admin-image-test-result" role="status" style="font-size:12px;color:var(--g500);margin-top:8px;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Embedding Models ───────────────────────────────────────── -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-network-wired"></i> Embedding Models — Learning Hub semantic search</h3>
|
|
<span id="admin-embed-provider-badge" style="font-size:11px;padding:2px 8px;border-radius:10px;background:var(--g100);color:var(--g600);">Loading...</span>
|
|
</div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:14px;">
|
|
<div id="admin-embed-info" style="font-size:12px;color:var(--g500);">Loading...</div>
|
|
<p style="font-size:12px;color:var(--g500);margin:0;">Corpus (MCP) embeddings are configured on the indexing service, not here.</p>
|
|
|
|
<!-- Search embedding models from provider -->
|
|
<div>
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;margin-bottom:8px;">Search Models from Provider</label>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
|
<input type="text" id="admin-embed-search" placeholder="Filter models (e.g. embedding, vertex)" style="font-size:13px;padding:6px 10px;border:1px solid var(--g300);border-radius:6px;flex:1;min-width:180px;">
|
|
<button id="btn-discover-embeddings" class="btn-sm btn-primary"><i class="fas fa-magnifying-glass"></i> Search</button>
|
|
</div>
|
|
<div id="admin-embed-discovered" style="margin-top:8px;display:flex;flex-direction:column;gap:4px;max-height:300px;overflow-y:auto;"></div>
|
|
<p id="admin-embed-discover-hint" style="font-size:12px;color:var(--g500);margin:6px 0 0;">Click Search to query available embedding models from your configured provider.</p>
|
|
</div>
|
|
|
|
<!-- Test Embedding -->
|
|
<div style="border-top:1px solid var(--g100);padding-top:12px;">
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;margin-bottom:8px;">Test Embedding</label>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
|
<input type="text" id="admin-embed-test-text" value="Pediatric patient with fever and cough" style="font-size:13px;padding:6px 10px;border:1px solid var(--g300);border-radius:6px;flex:1;min-width:200px;" placeholder="Sample text to embed...">
|
|
<button id="btn-test-embedding" class="btn-sm btn-primary"><i class="fas fa-code-branch"></i> Generate</button>
|
|
</div>
|
|
<div id="admin-embed-result" style="margin-top:8px;font-size:12px;color:var(--g500);"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── CMS: Reset to Defaults ─────────────────────────────────── -->
|
|
<div class="card" style="border:1px solid var(--red-light);">
|
|
<div class="card-header"><h3 style="color:var(--red);"><i class="fas fa-triangle-exclamation"></i> Reset Settings</h3></div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:10px;">
|
|
<p style="font-size:13px;color:var(--g600);margin:0;">Reset all announcement, feature flag, and email settings back to factory defaults. SMTP configuration and custom models are preserved.</p>
|
|
<div>
|
|
<button id="btn-reset-all-defaults" class="btn-sm" style="background:var(--red);color:white;border:none;border-radius:6px;padding:6px 14px;font-size:13px;cursor:pointer;"><i class="fas fa-rotate-left"></i> Reset All to Defaults</button>
|
|
</div>
|
|
</div>
|
|
</div>
|