// Settings page: // 1. Model pricing table — every model in the default price-table is one // row; input+output columns are editable, edits persist to // localStorage via settings-model.setOverride and the trace cost badges // pick them up through window.__dshPriceTable at read time. // 2. API keys — display-only presence chart for the four env vars the // shell talks to (DEEPSEEK, OPENAI, ANTHROPIC, service token). NEVER // renders a key value; only "set" / "not set". Personal vs. service // concept is explained in one paragraph. // // This module is a pure DOM controller reading from settings-model.js // (pure) and window.__dshPriceTable (in-memory table). No fetches; the // key-presence read comes through window.dsh.runtimeStatus (which the // main process already computes when starting a runtime — DEEPSEEK-only // today; the other three fall through to "not set"). 'use strict' ;(function () { const rootId = 'settings-pane' // Two-decimal fixed formatter used by the input display value. The // stored value stays as a number; only the visible text is truncated // so the researcher sees clean rate columns. function fmt(n) { if (typeof n !== 'number' || !isFinite(n)) return '' return n.toFixed(2) } function renderPricingRow(model, defaultRow, overrideRow) { const tr = document.createElement('tr') tr.dataset.model = model if (overrideRow && (overrideRow.input != null || overrideRow.output != null)) { tr.classList.add('settings-price-row--override') } const nameCell = document.createElement('td') nameCell.className = 'settings-price-model mono' nameCell.textContent = model tr.appendChild(nameCell) for (const field of ['input', 'output']) { const cell = document.createElement('td') cell.className = 'settings-price-cell' const input = document.createElement('input') input.type = 'number' input.step = '0.01' input.min = '0' input.className = 'settings-price-input mono' input.dataset.field = field const cur = (overrideRow && overrideRow[field] != null) ? overrideRow[field] : (defaultRow && defaultRow[field]) input.value = fmt(cur) const marker = document.createElement('span') marker.className = 'settings-price-marker' marker.title = 'This value overrides the default; press Reset to remove.' marker.textContent = 'edited' if (!(overrideRow && overrideRow[field] != null)) marker.hidden = true cell.appendChild(input) cell.appendChild(marker) tr.appendChild(cell) } const actions = document.createElement('td') actions.className = 'settings-price-actions' const reset = document.createElement('button') reset.className = 'ghost small' reset.type = 'button' reset.textContent = 'Reset' reset.title = 'Clear override for this model. Rate returns to the default table.' reset.disabled = !overrideRow || (overrideRow.input == null && overrideRow.output == null) reset.dataset.action = 'reset' actions.appendChild(reset) tr.appendChild(actions) return tr } function renderPricingTable(container, defaultTable, model) { const body = container.querySelector('[data-settings-price-tbody]') if (!body) return body.innerHTML = '' const overrides = model.readOverrides() const pricing = (defaultTable && defaultTable.pricing) || {} // Union of default-table rows and any overrides that were added for a // model the default table doesn't ship — the researcher may have // added a custom row via localStorage; we still render it so it can // be reset back to nothing. const models = new Set([...Object.keys(pricing), ...Object.keys(overrides)]) const sorted = Array.from(models).sort() for (const m of sorted) { body.appendChild(renderPricingRow(m, pricing[m], overrides[m])) } } // render keys as a resource table with the // LangSmith Settings > API Keys columns (Name / Tier / Description / // Presence / Last used). Falls back to the legacy