privateboard 0.1.2 → 0.1.4
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/dist/cli.js +3357 -928
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/public/agent-profile.js +17 -7
- package/public/app.js +3775 -434
- package/public/index.html +2524 -269
- package/public/onboarding.js +36 -9
- package/public/quote-cta.css +49 -5
- package/public/quote-cta.js +215 -17
- package/public/report/spines/a16z-thesis.css +212 -97
- package/public/report/spines/anthropic-essay.css +564 -221
- package/public/report/spines/boardroom-dark.css +130 -72
- package/public/report/spines/gartner-note.css +83 -48
- package/public/report/spines/mckinsey-deck.css +81 -31
- package/public/report/spines/openai-paper.css +96 -35
- package/public/report.html +3576 -197
- package/public/room-settings.js +11 -8
- package/public/themes.css +15 -2
- package/public/user-settings.css +19 -8
- package/public/user-settings.js +37 -162
package/public/room-settings.js
CHANGED
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"Long-form thinking aloud. Directors take the room slowly — pause to think, surface caveats, sit with ambiguity rather than rushing to resolve. Best for novel / ambiguous problems where premature conclusions cost more than slow ones.",
|
|
32
32
|
sharp:
|
|
33
33
|
"No hedging. Directors land each turn on a load-bearing claim and back it with the load-bearing reason. They cut the qualifying language (\"perhaps,\" \"could be,\" \"in some cases\") in favour of clear, falsifiable statements. Default for most rooms.",
|
|
34
|
-
|
|
35
|
-
"
|
|
34
|
+
terse:
|
|
35
|
+
"Telegraphic. One paragraph, often one sentence. Directors cut every warm-up, every diplomatic packaging, every \"I think\" — they state the conclusion and only justify if pressed. NOTE · this is the LENGTH dial, not the harshness dial. Whether a director pushes back hard or builds with you is set by Tone (brainstorm vs critique etc); Terse only decides how long they take saying it.",
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
/** Generic info popover · single floating element, hover-driven.
|
|
@@ -338,9 +338,9 @@
|
|
|
338
338
|
<span class="rs-chip-label">Sharp</span>
|
|
339
339
|
<span class="rs-chip-info rs-info-trigger" data-info-title="Sharp" data-info-body="${escape(INTENSITY_TIPS.sharp)}" tabindex="-1" aria-label="What 'Sharp' means">i</span>
|
|
340
340
|
</button>
|
|
341
|
-
<button type="button" class="rs-chip rs-chip-mini" data-rs-intensity-pick="
|
|
342
|
-
<span class="rs-chip-label">
|
|
343
|
-
<span class="rs-chip-info rs-info-trigger" data-info-title="
|
|
341
|
+
<button type="button" class="rs-chip rs-chip-mini" data-rs-intensity-pick="terse">
|
|
342
|
+
<span class="rs-chip-label">Terse</span>
|
|
343
|
+
<span class="rs-chip-info rs-info-trigger" data-info-title="Terse" data-info-body="${escape(INTENSITY_TIPS.terse)}" tabindex="-1" aria-label="What 'Terse' means">i</span>
|
|
344
344
|
</button>
|
|
345
345
|
</div>
|
|
346
346
|
</div>
|
|
@@ -539,7 +539,7 @@
|
|
|
539
539
|
}
|
|
540
540
|
|
|
541
541
|
function renderIntensity() {
|
|
542
|
-
// Intensity is now a 3-chip row (Calm / Sharp /
|
|
542
|
+
// Intensity is now a 3-chip row (Calm / Sharp / Terse) instead of
|
|
543
543
|
// a slider · highlight the active chip. The hint line above shows
|
|
544
544
|
// "currently: <value>" so the picked state stays self-evident.
|
|
545
545
|
const cur = effective("intensity");
|
|
@@ -696,7 +696,10 @@
|
|
|
696
696
|
renderConfirmState();
|
|
697
697
|
}
|
|
698
698
|
function stageIntensity(next) {
|
|
699
|
-
|
|
699
|
+
// Accept legacy `brutal` from any code path that still emits it
|
|
700
|
+
// (cached HTML, third-party clients) and normalize to `terse`.
|
|
701
|
+
if (next === "brutal") next = "terse";
|
|
702
|
+
if (!["calm", "sharp", "terse"].includes(next)) return;
|
|
700
703
|
STAGED.intensity = next === ROOM_STATE.intensity ? null : next;
|
|
701
704
|
renderIntensity();
|
|
702
705
|
renderConfirmState();
|
|
@@ -1001,7 +1004,7 @@
|
|
|
1001
1004
|
const rect = bar.getBoundingClientRect();
|
|
1002
1005
|
if (rect.width <= 0) return "sharp";
|
|
1003
1006
|
const ratio = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
|
|
1004
|
-
return ratio < 0.33 ? "calm" : ratio < 0.67 ? "sharp" : "
|
|
1007
|
+
return ratio < 0.33 ? "calm" : ratio < 0.67 ? "sharp" : "terse";
|
|
1005
1008
|
};
|
|
1006
1009
|
bar.addEventListener("pointerdown", (e) => {
|
|
1007
1010
|
e.preventDefault();
|
package/public/themes.css
CHANGED
|
@@ -45,12 +45,25 @@
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
:root {
|
|
48
|
-
|
|
48
|
+
/* CJK fallback chain · PingFang SC anchors every stack so Chinese
|
|
49
|
+
glyphs always render as PingFang on macOS rather than falling
|
|
50
|
+
through to whatever `system-ui` resolves to (Songti / Microsoft
|
|
51
|
+
YaHei / Source Han depending on platform). Latin fonts stay
|
|
52
|
+
FIRST so mixed CN/EN prose still picks Human Sans / Inter / Agent
|
|
53
|
+
for English glyphs and only Chinese characters land on PingFang
|
|
54
|
+
via per-glyph fallback. */
|
|
55
|
+
--font-human: "Human Sans", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue",
|
|
56
|
+
"PingFang SC", "PingFang TC", "Hiragino Sans GB", "Microsoft YaHei",
|
|
57
|
+
"Source Han Sans CN", "Noto Sans CJK SC",
|
|
58
|
+
system-ui, sans-serif;
|
|
49
59
|
/* Agent italic-faced font. ui-sans-serif is a CSS generic keyword
|
|
50
60
|
(must be unquoted) and resolves to the OS default sans-serif before
|
|
51
61
|
hitting -apple-system / system-ui — gives a clean italic synth on
|
|
52
62
|
systems where the bundled "Agent" face fails to load. */
|
|
53
|
-
--font-agent: "Agent", ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue",
|
|
63
|
+
--font-agent: "Agent", ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue",
|
|
64
|
+
"PingFang SC", "PingFang TC", "Hiragino Sans GB", "Microsoft YaHei",
|
|
65
|
+
"Source Han Sans CN", "Noto Sans CJK SC",
|
|
66
|
+
system-ui, sans-serif;
|
|
54
67
|
/* --sans defaults to the human face — chat input, topic question,
|
|
55
68
|
replies, and live notes use it unless an agent override applies. */
|
|
56
69
|
--sans: var(--font-human);
|
package/public/user-settings.css
CHANGED
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
}
|
|
176
176
|
.us-pane-tag {
|
|
177
177
|
font-family: var(--mono);
|
|
178
|
-
font-size:
|
|
178
|
+
font-size: 14px;
|
|
179
179
|
font-weight: 700;
|
|
180
180
|
letter-spacing: 0.2em;
|
|
181
181
|
text-transform: uppercase;
|
|
@@ -321,6 +321,17 @@
|
|
|
321
321
|
letter-spacing: -0.003em;
|
|
322
322
|
width: 100%;
|
|
323
323
|
}
|
|
324
|
+
/* API-key masking · the input is `type="text"` so browsers don't pop
|
|
325
|
+
up "Save password?" when the user types a key and navigates away.
|
|
326
|
+
`-webkit-text-security: disc` shows dots instead of the actual
|
|
327
|
+
characters — visually equivalent to a password field, but invisible
|
|
328
|
+
to password managers. Toggle (eye button) just removes this class.
|
|
329
|
+
`text-security` is the standard property name; only Safari + Chrome
|
|
330
|
+
actually implement it under the `-webkit-` prefix today. */
|
|
331
|
+
.us-input-masked {
|
|
332
|
+
-webkit-text-security: disc;
|
|
333
|
+
text-security: disc;
|
|
334
|
+
}
|
|
324
335
|
.us-input::placeholder { color: var(--text-faint, #3A382F); }
|
|
325
336
|
/* When a key is on file, we surface a 4+4 masked preview AS the
|
|
326
337
|
placeholder. Default placeholder colour is the dim "hint" tier,
|
|
@@ -394,7 +405,7 @@
|
|
|
394
405
|
.us-theme-info { min-width: 0; }
|
|
395
406
|
.us-theme-name {
|
|
396
407
|
font-family: var(--mono);
|
|
397
|
-
font-size:
|
|
408
|
+
font-size: 14px;
|
|
398
409
|
font-weight: 700;
|
|
399
410
|
color: var(--text, #C8C5BE);
|
|
400
411
|
letter-spacing: -0.005em;
|
|
@@ -442,7 +453,7 @@
|
|
|
442
453
|
}
|
|
443
454
|
.us-key-label {
|
|
444
455
|
font-family: var(--mono);
|
|
445
|
-
font-size:
|
|
456
|
+
font-size: 14px;
|
|
446
457
|
font-weight: 700;
|
|
447
458
|
color: var(--text, #C8C5BE);
|
|
448
459
|
letter-spacing: -0.003em;
|
|
@@ -824,7 +835,7 @@
|
|
|
824
835
|
flex: 0 0 auto;
|
|
825
836
|
}
|
|
826
837
|
.us-model-name {
|
|
827
|
-
font-size:
|
|
838
|
+
font-size: 14px;
|
|
828
839
|
color: var(--text, #C8C5BE);
|
|
829
840
|
font-weight: 600;
|
|
830
841
|
letter-spacing: -0.005em;
|
|
@@ -894,7 +905,7 @@
|
|
|
894
905
|
.us-agent-row:last-child { border-bottom: none; }
|
|
895
906
|
.us-agent-name-col { display: inline-flex; align-items: baseline; gap: 8px; min-width: 0; }
|
|
896
907
|
.us-agent-name {
|
|
897
|
-
font-size:
|
|
908
|
+
font-size: 14px;
|
|
898
909
|
color: var(--text, #C8C5BE);
|
|
899
910
|
font-weight: 600;
|
|
900
911
|
white-space: nowrap;
|
|
@@ -1073,7 +1084,7 @@
|
|
|
1073
1084
|
.us-models-row:last-child { border-bottom: none; }
|
|
1074
1085
|
.us-models-name {
|
|
1075
1086
|
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
1076
|
-
font-size:
|
|
1087
|
+
font-size: 14px;
|
|
1077
1088
|
font-weight: 700;
|
|
1078
1089
|
color: var(--text, #C8C5BE);
|
|
1079
1090
|
letter-spacing: -0.005em;
|
|
@@ -1148,7 +1159,7 @@
|
|
|
1148
1159
|
}
|
|
1149
1160
|
.us-models-default-name {
|
|
1150
1161
|
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
1151
|
-
font-size:
|
|
1162
|
+
font-size: 14px;
|
|
1152
1163
|
font-weight: 700;
|
|
1153
1164
|
color: var(--text, #C8C5BE);
|
|
1154
1165
|
letter-spacing: -0.005em;
|
|
@@ -1233,7 +1244,7 @@
|
|
|
1233
1244
|
.us-default-row-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
1234
1245
|
.us-default-row-name {
|
|
1235
1246
|
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
1236
|
-
font-size:
|
|
1247
|
+
font-size: 14px;
|
|
1237
1248
|
font-weight: 700;
|
|
1238
1249
|
color: var(--text, #C8C5BE);
|
|
1239
1250
|
letter-spacing: -0.005em;
|
package/public/user-settings.js
CHANGED
|
@@ -156,48 +156,12 @@
|
|
|
156
156
|
} catch (e) { /* swallow · UI is optimistic */ }
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
anthropic: "opus-4-7",
|
|
166
|
-
openai: "gpt-5-5",
|
|
167
|
-
google: "gemini-3-flash",
|
|
168
|
-
xai: "grok-4-3",
|
|
169
|
-
deepseek: "deepseek-v4-pro",
|
|
170
|
-
openrouter: "opus-4-7",
|
|
171
|
-
};
|
|
172
|
-
function primaryModelForProvider(provider) {
|
|
173
|
-
const snap = modelsSnapshot();
|
|
174
|
-
const reachable = (snap && snap.reachable) || [];
|
|
175
|
-
// 1. Curated primary, if reachable.
|
|
176
|
-
const curated = PRIMARY_BY_PROVIDER[provider];
|
|
177
|
-
if (curated && reachable.find((m) => m.modelV === curated && m.provider === provider)) {
|
|
178
|
-
return curated;
|
|
179
|
-
}
|
|
180
|
-
// 2. First reachable model from that provider.
|
|
181
|
-
const first = reachable.find((m) => m.provider === provider);
|
|
182
|
-
return first ? first.modelV : null;
|
|
183
|
-
}
|
|
184
|
-
/** Look up which provider currently owns the saved default model. */
|
|
185
|
-
function currentDefaultProvider() {
|
|
186
|
-
const snap = modelsSnapshot();
|
|
187
|
-
if (!snap || !snap.defaultModelV) return null;
|
|
188
|
-
const m = (snap.reachable || []).find((x) => x.modelV === snap.defaultModelV);
|
|
189
|
-
return m ? m.provider : null;
|
|
190
|
-
}
|
|
191
|
-
/** Click handler for the per-row "Set as default" button. Picks the
|
|
192
|
-
* provider's primary model + persists it as defaultModelV. The row
|
|
193
|
-
* re-renders so the badge moves. */
|
|
194
|
-
async function setProviderAsDefault(provider) {
|
|
195
|
-
const modelV = primaryModelForProvider(provider);
|
|
196
|
-
if (!modelV) return;
|
|
197
|
-
await saveDefaultModel(modelV);
|
|
198
|
-
// Re-render the keys section so every LLM row updates its badge.
|
|
199
|
-
if (currentSection === "keys") renderSection("keys");
|
|
200
|
-
}
|
|
159
|
+
// Provider→primary-model helpers (`primaryModelForProvider`,
|
|
160
|
+
// `currentDefaultProvider`, `setProviderAsDefault`) lived here to
|
|
161
|
+
// power the per-row "set as default" button on the API Key pane.
|
|
162
|
+
// That button + its companion bottom-of-pane Default Model picker
|
|
163
|
+
// were removed in favour of the dedicated "Default Model" sidebar
|
|
164
|
+
// pane (single source of truth). Helpers deleted as dead code.
|
|
201
165
|
|
|
202
166
|
// Set / clear a single provider key. The server applies the trim+empty-=delete
|
|
203
167
|
// semantic; we mirror the resulting meta into our local cache.
|
|
@@ -500,35 +464,29 @@
|
|
|
500
464
|
const placeholder = has
|
|
501
465
|
? (preview || "••••••••")
|
|
502
466
|
: p.placeholder;
|
|
503
|
-
// Default-
|
|
504
|
-
//
|
|
505
|
-
//
|
|
506
|
-
//
|
|
507
|
-
//
|
|
508
|
-
|
|
509
|
-
const isDefault = isLlm && currentDefaultProvider() === p.id;
|
|
510
|
-
const canSetDefault = isLlm && has && !isDefault && !!primaryModelForProvider(p.id);
|
|
511
|
-
const labelExtras = isDefault
|
|
512
|
-
? ' <span class="badge us-key-default-badge">default</span>'
|
|
513
|
-
: "";
|
|
467
|
+
// Default-model selection lives entirely in the dedicated
|
|
468
|
+
// "Default Model" sidebar pane. The previous in-row "default"
|
|
469
|
+
// badge + "set as default" button on each LLM provider was a
|
|
470
|
+
// duplicate UX that also competed with the bottom-of-pane
|
|
471
|
+
// "Default model" picker · all three controls did the same
|
|
472
|
+
// thing. The single source of truth is now the sidebar pane.
|
|
514
473
|
return `
|
|
515
|
-
<div class="us-key-row
|
|
474
|
+
<div class="us-key-row" data-provider="${p.id}">
|
|
516
475
|
<div class="us-key-head">
|
|
517
|
-
<div class="us-key-label">${escape(p.label)}
|
|
476
|
+
<div class="us-key-label">${escape(p.label)}</div>
|
|
518
477
|
<div class="us-key-status ${has ? "on" : "off"}" data-status>${has ? "● configured" : "○ not set"}</div>
|
|
519
|
-
${canSetDefault ? `<button type="button" class="us-key-set-default" data-set-default-provider="${p.id}" title="Use ${escape(p.label)} as the default model provider for new agents">set as default</button>` : ""}
|
|
520
478
|
${removable ? `<button type="button" class="us-key-remove" data-remove-provider="${p.id}" title="Remove">✕</button>` : ""}
|
|
521
479
|
</div>
|
|
522
480
|
<div class="us-key-hint">${escape(p.hint)}</div>
|
|
523
481
|
<div class="us-input-wrap">
|
|
524
482
|
<input
|
|
525
|
-
type="
|
|
526
|
-
class="us-input${has ? " has-preview" : ""}"
|
|
483
|
+
type="text"
|
|
484
|
+
class="us-input us-input-masked${has ? " has-preview" : ""}"
|
|
527
485
|
data-key-input
|
|
528
486
|
name="bk-${p.id}"
|
|
529
487
|
placeholder="${escape(placeholder)}"
|
|
530
488
|
value=""
|
|
531
|
-
autocomplete="
|
|
489
|
+
autocomplete="off"
|
|
532
490
|
data-lpignore="true"
|
|
533
491
|
data-1p-ignore="true"
|
|
534
492
|
data-form-type="other"
|
|
@@ -659,43 +617,15 @@
|
|
|
659
617
|
`;
|
|
660
618
|
}).join("");
|
|
661
619
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
const models = byProvider.get(p);
|
|
667
|
-
return `<optgroup label="${escape(providerLabel(p))}">${
|
|
668
|
-
models.map((m) => `<option value="${escape(m.modelV)}"${m.modelV === def ? " selected" : ""}>${escape(m.displayName)}</option>`).join("")
|
|
669
|
-
}</optgroup>`;
|
|
670
|
-
}).join("");
|
|
671
|
-
defaultBlock = `
|
|
672
|
-
<div class="us-models-default">
|
|
673
|
-
<div class="us-models-default-label">Default model</div>
|
|
674
|
-
<div class="us-models-default-hint">new agents inherit this. when an agent's model goes unreachable, it falls back here too.</div>
|
|
675
|
-
<div class="us-input-wrap us-models-default-wrap">
|
|
676
|
-
<select class="us-input us-models-default-select" data-default-model>${optgroups}</select>
|
|
677
|
-
</div>
|
|
678
|
-
</div>
|
|
679
|
-
`;
|
|
680
|
-
} else {
|
|
681
|
-
const m = reachable[0];
|
|
682
|
-
defaultBlock = `
|
|
683
|
-
<div class="us-models-default">
|
|
684
|
-
<div class="us-models-default-label">Default model</div>
|
|
685
|
-
<div class="us-models-default-static">
|
|
686
|
-
<span class="us-models-default-name">${escape(m.displayName)}</span>
|
|
687
|
-
<span class="us-models-default-note">only reachable model</span>
|
|
688
|
-
</div>
|
|
689
|
-
</div>
|
|
690
|
-
`;
|
|
691
|
-
}
|
|
692
|
-
|
|
620
|
+
// Default-model selection moved to the sidebar's "Default Model"
|
|
621
|
+
// pane · the previous bottom-of-pane select duplicated that flow.
|
|
622
|
+
// The Available Models block is now read-only (which models are
|
|
623
|
+
// reachable + how they route), nothing else.
|
|
693
624
|
return `
|
|
694
625
|
<div class="us-key-group us-key-group-models">
|
|
695
626
|
<div class="us-key-group-tag">Available models</div>
|
|
696
627
|
<div class="us-key-group-deck">${reachable.length} model${reachable.length === 1 ? "" : "s"} reachable across ${providers.length} provider${providers.length === 1 ? "" : "s"}. <code>direct</code> uses the provider key, <code>OR</code> routes through OpenRouter.</div>
|
|
697
628
|
<div class="us-models-list">${blocks}</div>
|
|
698
|
-
${defaultBlock}
|
|
699
629
|
</div>
|
|
700
630
|
`;
|
|
701
631
|
}
|
|
@@ -807,61 +737,10 @@
|
|
|
807
737
|
const slot = paneEl.querySelector("[data-models-summary]");
|
|
808
738
|
if (!slot) return;
|
|
809
739
|
slot.innerHTML = modelsSummaryHTML();
|
|
810
|
-
//
|
|
811
|
-
//
|
|
812
|
-
//
|
|
813
|
-
//
|
|
814
|
-
// default-related controls in place so we don't disturb the
|
|
815
|
-
// input field the user is typing into.
|
|
816
|
-
const defaultProvider = currentDefaultProvider();
|
|
817
|
-
paneEl.querySelectorAll(".us-key-row").forEach((row) => {
|
|
818
|
-
const id = row.dataset.provider;
|
|
819
|
-
const p = PROVIDERS.find((x) => x.id === id);
|
|
820
|
-
if (!p || p.group !== "llm") return;
|
|
821
|
-
const meta = _keysMeta[id];
|
|
822
|
-
const has = !!(meta && meta.configured);
|
|
823
|
-
const isDefault = defaultProvider === id;
|
|
824
|
-
const canSet = has && !isDefault && !!primaryModelForProvider(id);
|
|
825
|
-
// Toggle .is-default on the row.
|
|
826
|
-
row.classList.toggle("is-default", isDefault);
|
|
827
|
-
// Sync the badge in the label.
|
|
828
|
-
const label = row.querySelector(".us-key-label");
|
|
829
|
-
if (label) {
|
|
830
|
-
const existing = label.querySelector(".us-key-default-badge");
|
|
831
|
-
if (isDefault && !existing) {
|
|
832
|
-
label.insertAdjacentHTML("beforeend", ' <span class="badge us-key-default-badge">default</span>');
|
|
833
|
-
} else if (!isDefault && existing) {
|
|
834
|
-
existing.remove();
|
|
835
|
-
// Cleanup adjacent whitespace text node so we don't accumulate
|
|
836
|
-
// spaces over time.
|
|
837
|
-
const next = label.lastChild;
|
|
838
|
-
if (next && next.nodeType === 3 && /\s+/.test(next.nodeValue || "")) next.remove();
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
// Sync the "set as default" button.
|
|
842
|
-
const head = row.querySelector(".us-key-head");
|
|
843
|
-
if (head) {
|
|
844
|
-
const existing = head.querySelector("[data-set-default-provider]");
|
|
845
|
-
if (canSet && !existing) {
|
|
846
|
-
// Insert just before the remove button (or at the end).
|
|
847
|
-
const btn = document.createElement("button");
|
|
848
|
-
btn.type = "button";
|
|
849
|
-
btn.className = "us-key-set-default";
|
|
850
|
-
btn.dataset.setDefaultProvider = id;
|
|
851
|
-
btn.title = `Use ${p.label} as the default model provider for new agents`;
|
|
852
|
-
btn.textContent = "set as default";
|
|
853
|
-
btn.addEventListener("click", async (e) => {
|
|
854
|
-
e.preventDefault();
|
|
855
|
-
await setProviderAsDefault(id);
|
|
856
|
-
});
|
|
857
|
-
const removeBtn = head.querySelector("[data-remove-provider]");
|
|
858
|
-
if (removeBtn) head.insertBefore(btn, removeBtn);
|
|
859
|
-
else head.appendChild(btn);
|
|
860
|
-
} else if (!canSet && existing) {
|
|
861
|
-
existing.remove();
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
});
|
|
740
|
+
// Default-model state lives in the sidebar's "Default Model"
|
|
741
|
+
// pane · this refresh used to also patch each LLM row's
|
|
742
|
+
// badge / "set as default" button, but those controls were
|
|
743
|
+
// removed to eliminate the duplicate flow.
|
|
865
744
|
}
|
|
866
745
|
|
|
867
746
|
/* Avatar generation · same flow as the agent profile's regenerate
|
|
@@ -1013,11 +892,18 @@
|
|
|
1013
892
|
}
|
|
1014
893
|
|
|
1015
894
|
function wireKeysSection() {
|
|
895
|
+
// Show/hide toggle · the input is permanently `type="text"` so
|
|
896
|
+
// browsers don't trigger their "Save password?" popup when the
|
|
897
|
+
// user navigates away from a typed-in key (e.g., clicking another
|
|
898
|
+
// sidebar tab in user prefs). Masking is done via the CSS
|
|
899
|
+
// `-webkit-text-security: disc` rule on `.us-input-masked` —
|
|
900
|
+
// visually identical to a password input but invisible to
|
|
901
|
+
// password managers. Toggle = add/remove the masking class.
|
|
1016
902
|
paneEl.querySelectorAll("[data-key-eye]").forEach((btn) => {
|
|
1017
903
|
btn.addEventListener("click", (e) => {
|
|
1018
904
|
e.preventDefault();
|
|
1019
905
|
const input = btn.parentElement.querySelector("input");
|
|
1020
|
-
if (input) input.
|
|
906
|
+
if (input) input.classList.toggle("us-input-masked");
|
|
1021
907
|
});
|
|
1022
908
|
});
|
|
1023
909
|
|
|
@@ -1043,21 +929,10 @@
|
|
|
1043
929
|
});
|
|
1044
930
|
});
|
|
1045
931
|
|
|
1046
|
-
//
|
|
1047
|
-
//
|
|
1048
|
-
//
|
|
1049
|
-
|
|
1050
|
-
btn.addEventListener("click", async (e) => {
|
|
1051
|
-
e.preventDefault();
|
|
1052
|
-
const id = btn.dataset.setDefaultProvider;
|
|
1053
|
-
await setProviderAsDefault(id);
|
|
1054
|
-
});
|
|
1055
|
-
});
|
|
1056
|
-
|
|
1057
|
-
// Default-model picker · persists to /api/prefs.
|
|
1058
|
-
paneEl.querySelectorAll("[data-default-model]").forEach((sel) => {
|
|
1059
|
-
sel.addEventListener("change", () => { saveDefaultModel(sel.value); });
|
|
1060
|
-
});
|
|
932
|
+
// Default-model controls live in the sidebar's "Default Model"
|
|
933
|
+
// pane only · the previous in-row "set as default" button and
|
|
934
|
+
// the bottom-of-pane Default Model picker were removed because
|
|
935
|
+
// they duplicated that flow.
|
|
1061
936
|
|
|
1062
937
|
// Auto-save: every keystroke / paste persists immediately, no Save button.
|
|
1063
938
|
// We debounce slightly so we don't fire a server PUT on every character —
|