Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>ENCODE</title> | |
| <link rel="stylesheet" href="/styles.css?v=wave10" /> | |
| </head> | |
| <body> | |
| <header class="topbar"> | |
| <span class="brand">ENCODE</span> | |
| <span class="topbar-title">Clinical Code and Phenotype Search</span> | |
| <button id="help-btn" class="basket-btn" title="What this tool is and how to use it">About</button> | |
| <button id="basket-btn" class="basket-btn">Collection <span id="basket-count" class="basket-count">0</span></button> | |
| </header> | |
| <div class="layout"> | |
| <aside class="sidebar"> | |
| <!-- Mode switch. Hidden entirely when the planner has no API key | |
| configured, so a control is never shown that fails on click. | |
| Tab semantics so assistive tech announces which mode is active; | |
| `.on` alone is a purely visual signal. --> | |
| <div id="mode-switch" class="mode-switch hidden" role="tablist" aria-label="Search mode"> | |
| <button id="mode-search" class="mode-btn on" type="button" role="tab" | |
| aria-selected="true" aria-controls="search-controls">Search</button> | |
| <button id="mode-plan" class="mode-btn" type="button" role="tab" | |
| aria-selected="false" aria-controls="plan-controls">Agent</button> | |
| </div> | |
| <!-- Mode-specific: what to search. Only these two blocks swap. --> | |
| <div id="search-side"> | |
| <label class="side-label" for="category">Search For</label> | |
| <select id="category" class="category-select"> | |
| <optgroup label="Phenotype discovery"> | |
| <option value="phenotype">Phenotype</option> | |
| </optgroup> | |
| <optgroup label="Code lookup"> | |
| <option value="diagnosis">Diagnosis</option> | |
| <option value="medication">Medication</option> | |
| <option value="lab">Lab</option> | |
| <option value="procedure">Procedure</option> | |
| </optgroup> | |
| </select> | |
| </div> | |
| <div id="plan-side" class="plan-side hidden"> | |
| <label class="side-label" for="plan-model">Agent Model</label> | |
| <select id="plan-model" class="category-select"></select> | |
| <button id="plan-model-remove" class="side-link hidden" type="button">Remove this model</button> | |
| </div> | |
| <!-- Shared: these govern retrieval itself, so they apply equally to a | |
| query typed in the search box and to one a plan chip runs. Hiding | |
| them in Agent mode left them silently steering results that the | |
| user could neither see nor reach. --> | |
| <div id="system-filters" class="hidden"> | |
| <label class="side-label">Code Systems</label> | |
| <div id="systems" class="filters"></div> | |
| </div> | |
| <!-- The search box embeds its text and scores it against descriptions, | |
| so a code never retrieves itself: typing 90999 returns "time" and | |
| "introduction". This switches the one search bar over to the code | |
| column rather than adding a second box to the page. --> | |
| <div id="code-lookup" class="hidden"> | |
| <label class="side-label" for="lookup-mode">Look Up a Code</label> | |
| <label class="filter-opt" for="lookup-mode" | |
| title="Searches the code column for an exact match on what you type."> | |
| <input id="lookup-mode" type="checkbox" /> | |
| <span>Search by code</span> | |
| </label> | |
| </div> | |
| <details id="advanced" class="advanced"> | |
| <summary>Advanced</summary> | |
| <label class="side-label" for="model">Ranking Model</label> | |
| <select id="model" class="category-select"></select> | |
| <!-- A typed count, because a phenotype review wants tens of results | |
| and a lab review wants thousands. One server cap covers every | |
| category. The box is also its own slider: dragging it scrubs the | |
| value geometrically, hinted by the chevrons and the resize | |
| cursor. --> | |
| <label class="side-label" for="k">Number of Results</label> | |
| <input id="k" class="k-input" type="number" min="1" max="2000" step="1" value="50" | |
| title="Drag up or down to change it, or type a number." /> | |
| <!-- How many of the fetched rows each page shows. Client-side only: | |
| changing it redraws the table, it never re-runs the search. --> | |
| <label class="side-label" for="page-size">Results Per Page</label> | |
| <input id="page-size" class="k-input" type="number" min="1" max="500" step="1" value="50" | |
| title="Drag up or down to change it, or type a number." /> | |
| <!-- Grading is on for everyone now, so the control is hidden rather | |
| than removed: the input still anchors the persisted state, and | |
| deleting it would break the wiring below it in app.js. --> | |
| <label class="side-label hidden" for="review-mode">Validation & Feedback</label> | |
| <label class="filter-opt hidden" for="review-mode" | |
| title="Adds Relevant / Related / Not relevant to each result, and a submit box"> | |
| <input id="review-mode" type="checkbox" /> | |
| <span>Enable result grading</span> | |
| </label> | |
| </details> | |
| <button id="export" class="export hidden">Export CSV</button> | |
| </aside> | |
| <main class="content"> | |
| <div id="main-screen"> | |
| <div id="search-controls" role="tabpanel" aria-labelledby="mode-search"> | |
| <p id="search-for" class="search-for">Search for Diagnosis</p> | |
| <div class="search-row"> | |
| <!-- Own dropdown rather than <datalist>: it has to open on click | |
| with the whole history, not only while typing, and without the | |
| caret the browser paints inside a list-bound input. --> | |
| <div class="query-wrap"> | |
| <input id="query" class="query" type="text" autocomplete="off" | |
| role="combobox" aria-expanded="false" aria-autocomplete="list" | |
| aria-controls="recent-panel" placeholder="e.g. heart failure" /> | |
| <ul id="recent-panel" class="recent-panel hidden" role="listbox" | |
| aria-label="Recent searches"></ul> | |
| </div> | |
| <button id="search" class="search-btn">Search</button> | |
| </div> | |
| </div> | |
| <div id="plan-controls" class="hidden" role="tabpanel" aria-labelledby="mode-plan"> | |
| <p id="plan-for" class="search-for">Describe the cohort you are looking for</p> | |
| <div class="search-row"> | |
| <!-- Adapted from CIPHER use case 001 (CKD staging), written in the | |
| register the CIPHER phenotype records themselves use: methods | |
| prose, terms spelled out with the abbreviation in parentheses, | |
| evidence types joined with "along with". Deliberately not | |
| terse, so the box reads as somewhere to describe a cohort | |
| rather than somewhere to type a keyword. It still spans every | |
| surface the plan bar can show. The staging rules are left out: | |
| that logic is SAGE's, and negations and date arithmetic are | |
| what make planning slow. --> | |
| <textarea id="plan-query" class="query plan-query" rows="4" | |
| placeholder="e.g. We are putting together a chronic kidney disease cohort from Veterans Health Administration records, and we need the underlying code lists first. Patients should be identified from kidney related diagnoses and from kidney related procedures such as dialysis, along with the laboratory results the staging algorithm relies on: serum creatinine, estimated glomerular filtration rate (eGFR), and urine protein and albumin."></textarea> | |
| <button id="plan-btn" class="search-btn">Plan</button> | |
| </div> | |
| <p id="plan-status" class="status" aria-hidden="true"></p> | |
| <p id="plan-announce" class="sr-only" role="status" aria-live="polite"></p> | |
| <!-- Collapsed, this is only a counter: seconds elapsed and reasoning | |
| tokens spent. The transcript rolls underneath for anyone who | |
| opens it. aria-hidden: it ticks several times a second and is the | |
| model's working notes, not a result. --> | |
| <details id="cot" class="cot hidden" aria-hidden="true"> | |
| <summary id="cot-summary">Full reasoning</summary> | |
| <pre id="cot-text" class="cot-text"></pre> | |
| </details> | |
| </div> | |
| <!-- The plan survives searches: it sits outside #results, which is | |
| cleared on every category switch and every new search. --> | |
| <section id="plan-bar" class="plan-bar hidden"></section> | |
| <p id="status" class="status" role="status" aria-live="polite"></p> | |
| <section id="results" class="results"></section> | |
| <div id="empty" class="empty"></div> | |
| <div id="submit-row" class="submit-row hidden"> | |
| <input id="annotator" class="annotator" type="text" placeholder="Your name" /> | |
| <button id="submit" class="submit-btn">Submit Annotations</button> | |
| <span id="submit-msg" class="submit-msg"></span> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <section id="detail-page" class="detail-page" aria-hidden="true"> | |
| <div class="detail-topbar"> | |
| <button id="detail-back" class="detail-back">← Back to results</button> | |
| </div> | |
| <div id="detail-body" class="detail-body"></div> | |
| </section> | |
| <aside id="drawer" class="drawer hidden"> | |
| <button id="drawer-close" class="drawer-close" aria-label="Close">×</button> | |
| <div id="drawer-body"></div> | |
| </aside> | |
| <div id="overlay" class="overlay hidden"></div> | |
| <!-- Add-a-model dialog. Credentials entered here stay in this browser and | |
| are sent with each plan request so the server can make the call; the | |
| server never stores them. --> | |
| <div id="model-modal" class="modal hidden" role="dialog" aria-modal="true" | |
| aria-labelledby="mm-title"> | |
| <div class="modal-card"> | |
| <h3 id="mm-title">Add a model</h3> | |
| <label class="modal-label" for="mm-kind">API format</label> | |
| <select id="mm-kind" class="modal-input"> | |
| <option value="openai">OpenAI-compatible</option> | |
| <option value="anthropic">Anthropic (Claude)</option> | |
| </select> | |
| <label class="modal-label" for="mm-base">Base URL</label> | |
| <input id="mm-base" class="modal-input" type="url" autocomplete="off" spellcheck="false" /> | |
| <label class="modal-label" for="mm-model">Model name</label> | |
| <input id="mm-model" class="modal-input" type="text" autocomplete="off" spellcheck="false" /> | |
| <label class="modal-label" for="mm-key">API key</label> | |
| <input id="mm-key" class="modal-input" type="password" autocomplete="off" /> | |
| <p class="modal-note"> | |
| Your browser calls this model directly. The key stays in this browser — | |
| it is never sent to ENCODE or stored on our server. Only what the model | |
| produces is sent back to ENCODE, to be turned into search criteria. | |
| </p> | |
| <p id="mm-error" class="modal-error"></p> | |
| <div class="modal-actions"> | |
| <button id="mm-cancel" class="link" type="button">Cancel</button> | |
| <button id="mm-save" class="search-btn" type="button">Save model</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Shown when navigation would discard unsubmitted grades. --> | |
| <div id="leave-modal" class="modal hidden" role="dialog" aria-modal="true" | |
| aria-labelledby="lm-title"> | |
| <div class="modal-card"> | |
| <h3 id="lm-title">Unsubmitted grades</h3> | |
| <p class="modal-text">Leaving this result set discards the grades you have not submitted.</p> | |
| <div class="modal-actions"> | |
| <button id="lm-stay" class="link" type="button">Stay</button> | |
| <button id="lm-leave" class="link" type="button">Leave without submitting</button> | |
| <button id="lm-submit" class="search-btn" type="button">Submit and continue</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Shown once per session after an export, offering the grading step. --> | |
| <div id="export-modal" class="modal hidden" role="dialog" aria-modal="true" | |
| aria-labelledby="xm-title"> | |
| <div class="modal-card"> | |
| <h3 id="xm-title">Export saved</h3> | |
| <p class="modal-text">Your ratings tell us which results were actually useful. Grade the rows you used before you go?</p> | |
| <div class="modal-actions"> | |
| <button id="xm-later" class="link" type="button">Not now</button> | |
| <button id="xm-rate" class="search-btn" type="button">Rate results</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script src="/app.js?v=wave10"></script> | |
| </body> | |
| </html> | |