@ai-ecoverse/slicc-spectrum 1.91.0 → 1.93.0

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/slicc-ui.js CHANGED
@@ -9349,7 +9349,7 @@ function themeOf(node) {
9349
9349
  }
9350
9350
  return document.querySelector("slicc-app")?.shadowRoot?.querySelector("sp-theme") ?? document.body;
9351
9351
  }
9352
- var dialogStyles = i`
9352
+ var dialogFrame = i`
9353
9353
  dialog {
9354
9354
  box-sizing: border-box;
9355
9355
  max-inline-size: calc(100vw - 2 * var(--swc-spacing-300));
@@ -9369,6 +9369,13 @@ var dialogStyles = i`
9369
9369
  color-mix(in srgb, var(--swc-overlay-color) calc(var(--swc-overlay-opacity-dark) * 100%), transparent)
9370
9370
  );
9371
9371
  }
9372
+ @media (forced-colors: active) {
9373
+ dialog {
9374
+ outline: solid;
9375
+ }
9376
+ }
9377
+ `;
9378
+ var alertStyles = i`
9372
9379
  sp-alert-dialog {
9373
9380
  --mod-alert-dialog-min-width: min(
9374
9381
  var(--swc-alert-dialog-minimum-width),
@@ -9390,9 +9397,6 @@ var dialogStyles = i`
9390
9397
  }
9391
9398
  }
9392
9399
  @media (forced-colors: active) {
9393
- dialog {
9394
- outline: solid;
9395
- }
9396
9400
  sp-alert-dialog {
9397
9401
  border: none;
9398
9402
  }
@@ -9409,6 +9413,7 @@ var dialogStyles = i`
9409
9413
  margin-block-end: var(--swc-spacing-75);
9410
9414
  }
9411
9415
  `;
9416
+ var dialogStyles = [dialogFrame, alertStyles];
9412
9417
  var SliccConfirm = class extends i2 {
9413
9418
  static properties = { heading: {}, body: {}, action: {}, variant: {}, cancel: {} };
9414
9419
  trigger = null;
@@ -22649,6 +22654,34 @@ function markdown(text2) {
22649
22654
  return b`${parse(text2).map(render)}`;
22650
22655
  }
22651
22656
 
22657
+ // src/app/sign-in.ts
22658
+ var continuing = "Continue in the sign-in window that opened.";
22659
+ var closed = "Closed the sign-in window? Cancel, then Log in again.";
22660
+ var signInWait = { closedAfter: 2e4 };
22661
+ function signInText(waited) {
22662
+ return waited ? closed : continuing;
22663
+ }
22664
+ var SignInWaits = class {
22665
+ #since = /* @__PURE__ */ new Map();
22666
+ #update;
22667
+ constructor(update) {
22668
+ this.#update = update;
22669
+ }
22670
+ waited(key2) {
22671
+ const now = Date.now();
22672
+ const since3 = this.#since.get(key2);
22673
+ if (since3 === void 0) {
22674
+ this.#since.set(key2, now);
22675
+ setTimeout(this.#update, signInWait.closedAfter);
22676
+ return false;
22677
+ }
22678
+ return now - since3 >= signInWait.closedAfter;
22679
+ }
22680
+ done(key2) {
22681
+ this.#since.delete(key2);
22682
+ }
22683
+ };
22684
+
22652
22685
  // src/app/messages.ts
22653
22686
  var lickLabels = {
22654
22687
  webhook: ["Webhook", "informative"],
@@ -22843,8 +22876,6 @@ function actionLabel(action, handlers, messageId) {
22843
22876
  return count2 > 1 ? `Drop ${count2} failed turns` : actionLabels[action];
22844
22877
  }
22845
22878
  var signedIn = "Signed in. Retry to send it again.";
22846
- var continuing = "Continue in the sign-in window that opened.";
22847
- var closed = "Closed the sign-in window? Cancel, then Log in again.";
22848
22879
  function stopSigningIn(event2, handlers, account, id) {
22849
22880
  const main2 = event2.currentTarget.nextElementSibling;
22850
22881
  main2.focus();
@@ -22863,7 +22894,7 @@ function errorCard(item2, handlers) {
22863
22894
  const login = fix === "login" && item2.account && !handlers.readOnly ? handlers.login?.(item2.account, id) : void 0;
22864
22895
  const resolved = login?.state === "connected";
22865
22896
  const signing = login?.state === "signing-in" && login.window;
22866
- const text2 = resolved ? signedIn : signing ? login.waited ? closed : continuing : item2.text;
22897
+ const text2 = resolved ? signedIn : signing ? signInText(login.waited) : item2.text;
22867
22898
  const title = resolved && login.provider ? `Signed in to ${login.provider} again.` : item2.title;
22868
22899
  const failure2 = login?.state === "idle" ? login.error : void 0;
22869
22900
  return b`<div class="error-card" role=${resolved ? A : "alert"} aria-atomic=${resolved ? A : "false"} data-id=${id || A} data-login=${login?.state ?? A}>
@@ -24701,7 +24732,7 @@ document.addEventListener(
24701
24732
  },
24702
24733
  true
24703
24734
  );
24704
- var SliccChat = class _SliccChat extends ThemedElement {
24735
+ var SliccChat = class extends ThemedElement {
24705
24736
  static properties = {
24706
24737
  ...ThemedElement.properties,
24707
24738
  agent: {},
@@ -25125,19 +25156,14 @@ var SliccChat = class _SliccChat extends ThemedElement {
25125
25156
  if (!restored) return;
25126
25157
  this.renderRoot.querySelector("slicc-composer")?.restore(latest ? { text: latest.text, attachments: latest.attachments } : restored);
25127
25158
  }
25128
- static closedAfter = 2e4;
25129
- #logins = /* @__PURE__ */ new Map();
25130
- #signing = /* @__PURE__ */ new Map();
25131
- #waited(key2) {
25132
- const now = Date.now();
25133
- const since3 = this.#signing.get(key2);
25134
- if (since3 === void 0) {
25135
- this.#signing.set(key2, now);
25136
- setTimeout(() => this.requestUpdate(), _SliccChat.closedAfter);
25137
- return false;
25138
- }
25139
- return now - since3 >= _SliccChat.closedAfter;
25159
+ static get closedAfter() {
25160
+ return signInWait.closedAfter;
25161
+ }
25162
+ static set closedAfter(after) {
25163
+ signInWait.closedAfter = after;
25140
25164
  }
25165
+ #logins = /* @__PURE__ */ new Map();
25166
+ #waits = new SignInWaits(() => this.requestUpdate());
25141
25167
  #loginState(accountId, messageId) {
25142
25168
  const settings = this.model.settings;
25143
25169
  const account = settings.accounts().find((candidate) => candidate.id === accountId);
@@ -25146,8 +25172,8 @@ var SliccChat = class _SliccChat extends ThemedElement {
25146
25172
  const window2 = account?.auth === "oauth";
25147
25173
  const key2 = `${messageId}:${accountId}`;
25148
25174
  if (account?.status === "signing-in" || local2?.pending)
25149
- return { state: "signing-in", cancel, window: window2, waited: window2 && this.#waited(key2) };
25150
- this.#signing.delete(key2);
25175
+ return { state: "signing-in", cancel, window: window2, waited: window2 && this.#waits.waited(key2) };
25176
+ this.#waits.done(key2);
25151
25177
  if (account?.status === "connected") return { state: "connected", provider: account.provider };
25152
25178
  return { state: "idle", error: local2?.error };
25153
25179
  }
@@ -25988,13 +26014,13 @@ var SliccFileView = class extends ThemedElement {
25988
26014
  async save() {
25989
26015
  const model = this.model;
25990
26016
  if (!model || this.draft === null) return;
25991
- const draft = this.draft;
26017
+ const draft2 = this.draft;
25992
26018
  this.saving = true;
25993
26019
  try {
25994
- await model.files.write(this.path, draft);
25995
- this.text = draft;
26020
+ await model.files.write(this.path, draft2);
26021
+ this.text = draft2;
25996
26022
  this.missing = false;
25997
- if (this.draft === draft) this.draft = null;
26023
+ if (this.draft === draft2) this.draft = null;
25998
26024
  } finally {
25999
26025
  this.saving = false;
26000
26026
  }
@@ -39792,7 +39818,8 @@ var fieldStyles = i`
39792
39818
  font-size: var(--swc-font-size-75);
39793
39819
  color: var(--swc-neutral-subdued-content-color-default);
39794
39820
  }
39795
- .field input:not([type='checkbox']) {
39821
+ .field input:not([type='checkbox']),
39822
+ .field select {
39796
39823
  box-sizing: border-box;
39797
39824
  inline-size: 100%;
39798
39825
  font: inherit;
@@ -39803,7 +39830,8 @@ var fieldStyles = i`
39803
39830
  background: var(--swc-background-layer-2-color);
39804
39831
  color: inherit;
39805
39832
  }
39806
- .field input[aria-invalid='true'] {
39833
+ .field input[aria-invalid='true'],
39834
+ .field select[aria-invalid='true'] {
39807
39835
  border-color: var(--swc-negative-border-color-default);
39808
39836
  }
39809
39837
  .field input[type='file'] {
@@ -39820,6 +39848,7 @@ var fieldStyles = i`
39820
39848
  color: inherit;
39821
39849
  }
39822
39850
  .field input:focus-visible,
39851
+ .field select:focus-visible,
39823
39852
  input[type='checkbox']:focus-visible {
39824
39853
  outline: var(--swc-focus-indicator-thickness) solid var(--swc-focus-indicator-color);
39825
39854
  outline-offset: var(--swc-focus-ring-gap);
@@ -40419,7 +40448,7 @@ var SliccComposer = class extends ThemedElement {
40419
40448
  clearValues(this.renderRoot);
40420
40449
  const held = [
40421
40450
  this.attachments,
40422
- ...[...this.#drafts.values()].map((draft) => draft.attachments)
40451
+ ...[...this.#drafts.values()].map((draft2) => draft2.attachments)
40423
40452
  ];
40424
40453
  if (held.flat().some((attachment2) => attachment2.url?.startsWith("blob:")))
40425
40454
  this.#abandon = setTimeout(() => {
@@ -40475,10 +40504,10 @@ var SliccComposer = class extends ThemedElement {
40475
40504
  #switch(id) {
40476
40505
  if (this.#agent)
40477
40506
  this.#drafts.set(this.#agent, { value: this.value, attachments: this.attachments });
40478
- const draft = this.#drafts.get(id);
40507
+ const draft2 = this.#drafts.get(id);
40479
40508
  this.#agent = id;
40480
- this.value = draft?.value ?? "";
40481
- this.attachments = draft?.attachments ?? [];
40509
+ this.value = draft2?.value ?? "";
40510
+ this.attachments = draft2?.attachments ?? [];
40482
40511
  this.popup = null;
40483
40512
  this.#history = -1;
40484
40513
  }
@@ -40531,8 +40560,8 @@ var SliccComposer = class extends ThemedElement {
40531
40560
  this.failure = null;
40532
40561
  this.#history = -1;
40533
40562
  model.agent.send(this.#agent, text2).catch((error) => {
40534
- const draft = this.value.trim();
40535
- this.value = draft && draft !== text2 ? `${text2}
40563
+ const draft2 = this.value.trim();
40564
+ this.value = draft2 && draft2 !== text2 ? `${text2}
40536
40565
  ${this.value}` : text2;
40537
40566
  this.failure = error instanceof Error ? error.message : String(error);
40538
40567
  });
@@ -42870,6 +42899,7 @@ function recordKind(account) {
42870
42899
  return account.group === "mcp" ? "mcp" : "model";
42871
42900
  }
42872
42901
  function recordName(account) {
42902
+ if (account.vaultName) return account.vaultName;
42873
42903
  if (account.group === "mcp") return `MCP_${upper(serverOf(account))}_TOKEN`;
42874
42904
  return envNames[account.id] ?? `${upper(account.id)}_API_KEY`;
42875
42905
  }
@@ -42884,6 +42914,48 @@ var accountStatus = {
42884
42914
  "needs-sign-in": ["neutral", "Needs sign-in"],
42885
42915
  "sign-in-expired": ["notice", "Sign-in expired"]
42886
42916
  };
42917
+ var authHints = {
42918
+ "api-key": "API key",
42919
+ oauth: "Sign in",
42920
+ fields: "Endpoint and key",
42921
+ local: "On this device"
42922
+ };
42923
+ var listOrder = {
42924
+ connected: 0,
42925
+ expired: 1,
42926
+ "signing-in": 2,
42927
+ disconnected: 3
42928
+ };
42929
+ var corsNotice = {
42930
+ short: "Needs the SLICC extension or CLI",
42931
+ long: "Needs the SLICC extension or CLI, because the browser blocks direct requests to it."
42932
+ };
42933
+ var removeBodies = {
42934
+ oauth: "Its sign-in is deleted from the vault. Models from it stop working.",
42935
+ local: "Models from it stop working."
42936
+ };
42937
+ function draft(option2) {
42938
+ return {
42939
+ id: option2.id,
42940
+ provider: option2.name,
42941
+ identity: "",
42942
+ status: "disconnected",
42943
+ group: "providers",
42944
+ store: "vault",
42945
+ auth: option2.auth === "fields" ? "api-key" : option2.auth,
42946
+ fields: option2.fields,
42947
+ keyUrl: option2.keyUrl,
42948
+ needs: option2.needs,
42949
+ vaultName: option2.vaultName
42950
+ };
42951
+ }
42952
+ function matchProviders(options, query) {
42953
+ const words = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
42954
+ return options.filter((option2) => {
42955
+ const text2 = `${option2.name} ${option2.id} ${option2.description ?? ""}`.toLowerCase();
42956
+ return words.every((word) => text2.includes(word));
42957
+ });
42958
+ }
42887
42959
  var popup = (account) => account.group === "mcp" && account.auth === "oauth";
42888
42960
  var secretNames = { "api-key": "API key", token: "token" };
42889
42961
  var rejectedNames = { "api-key": "API key rejected", token: "Token rejected" };
@@ -42903,8 +42975,15 @@ var SliccSettings = class extends ThemedElement {
42903
42975
  entering: { state: true },
42904
42976
  failure: { state: true },
42905
42977
  problem: { state: true },
42906
- blocked: { state: true }
42978
+ blocked: { state: true },
42979
+ picking: { state: true },
42980
+ adding: { state: true },
42981
+ query: { state: true },
42982
+ filled: { state: true }
42907
42983
  };
42984
+ #waits = new SignInWaits(() => this.requestUpdate());
42985
+ #landing = null;
42986
+ #bases = /* @__PURE__ */ new Map();
42908
42987
  #scroll = new ViewScroll(this, "settings:scroll", { scroller: () => this, rows: "[data-id]" });
42909
42988
  constructor() {
42910
42989
  super();
@@ -42913,9 +42992,16 @@ var SliccSettings = class extends ThemedElement {
42913
42992
  this.failure = null;
42914
42993
  this.problem = null;
42915
42994
  this.blocked = false;
42995
+ this.picking = false;
42996
+ this.adding = null;
42997
+ this.query = "";
42998
+ this.filled = /* @__PURE__ */ new Set();
42916
42999
  }
42917
43000
  static styles = [
42918
43001
  fieldStyles,
43002
+ dialogFrame,
43003
+ searchCss,
43004
+ r(link_default),
42919
43005
  i`
42920
43006
  :host {
42921
43007
  display: block;
@@ -43027,6 +43113,144 @@ var SliccSettings = class extends ThemedElement {
43027
43113
  line-height: var(--swc-line-height-200);
43028
43114
  --swc-icon-color: var(--swc-negative-content-color-default);
43029
43115
  }
43116
+ .actions {
43117
+ display: flex;
43118
+ align-items: center;
43119
+ gap: var(--swc-spacing-75);
43120
+ }
43121
+ .notes {
43122
+ grid-column: 1 / -1;
43123
+ display: grid;
43124
+ gap: var(--swc-spacing-75);
43125
+ margin-block-start: calc(-1 * var(--swc-spacing-200));
43126
+ }
43127
+ .notice {
43128
+ display: flex;
43129
+ align-items: flex-start;
43130
+ gap: var(--swc-spacing-75);
43131
+ margin: 0;
43132
+ color: var(--swc-neutral-subdued-content-color-default);
43133
+ font-size: var(--swc-font-size-75);
43134
+ line-height: var(--swc-line-height-200);
43135
+ --swc-icon-color: var(--swc-notice-visual-color);
43136
+ }
43137
+ .notice swc-icon-alert-triangle {
43138
+ flex: none;
43139
+ margin-block-start: var(--swc-spacing-50);
43140
+ }
43141
+ .add {
43142
+ margin-block-start: var(--swc-spacing-200);
43143
+ margin-inline-start: calc(-1 * var(--swc-spacing-100));
43144
+ }
43145
+ dialog.picker {
43146
+ inline-size: min(520px, calc(100vw - 2 * var(--swc-spacing-300)));
43147
+ }
43148
+ .picker .head {
43149
+ display: flex;
43150
+ align-items: center;
43151
+ justify-content: space-between;
43152
+ gap: var(--swc-spacing-200);
43153
+ padding: var(--swc-spacing-400) var(--swc-spacing-300) var(--swc-spacing-200) var(--swc-spacing-500);
43154
+ }
43155
+ .picker h2 {
43156
+ margin: 0;
43157
+ font-size: var(--swc-heading-size-s);
43158
+ overflow-wrap: anywhere;
43159
+ }
43160
+ .picker .find {
43161
+ display: flex;
43162
+ padding: var(--swc-spacing-100) var(--swc-spacing-500) var(--swc-spacing-200);
43163
+ }
43164
+ .picker .results {
43165
+ block-size: min(400px, calc(100dvh - 280px));
43166
+ overflow-y: auto;
43167
+ overscroll-behavior: contain;
43168
+ padding: var(--swc-spacing-75) var(--swc-spacing-400);
43169
+ border-block: var(--swc-border-width-100) solid var(--swc-gray-200);
43170
+ }
43171
+ .options {
43172
+ display: grid;
43173
+ gap: var(--swc-spacing-50);
43174
+ }
43175
+ .option {
43176
+ display: grid;
43177
+ grid-template-columns: minmax(0, 1fr) max-content;
43178
+ align-items: baseline;
43179
+ gap: var(--swc-spacing-50) var(--swc-spacing-300);
43180
+ padding: var(--swc-spacing-100) var(--swc-spacing-100);
43181
+ border-radius: var(--swc-corner-radius-medium-default);
43182
+ cursor: pointer;
43183
+ }
43184
+ .option:hover,
43185
+ .option:focus {
43186
+ background: var(--swc-gray-200);
43187
+ }
43188
+ .option:focus {
43189
+ outline: none;
43190
+ }
43191
+ .option:focus-visible {
43192
+ outline: var(--swc-focus-indicator-thickness) solid var(--swc-focus-indicator-color);
43193
+ outline-offset: calc(-1 * var(--swc-focus-indicator-thickness));
43194
+ }
43195
+ .option[aria-disabled='true'] {
43196
+ cursor: default;
43197
+ }
43198
+ .option[aria-disabled='true']:hover {
43199
+ background: none;
43200
+ }
43201
+ .option .name {
43202
+ font-weight: var(--swc-bold-font-weight);
43203
+ overflow-wrap: anywhere;
43204
+ }
43205
+ .option[aria-disabled='true'] .name {
43206
+ color: var(--swc-neutral-subdued-content-color-default);
43207
+ }
43208
+ .option .tag {
43209
+ justify-self: end;
43210
+ color: var(--swc-neutral-subdued-content-color-default);
43211
+ font-size: var(--swc-font-size-75);
43212
+ }
43213
+ .added {
43214
+ display: inline-flex;
43215
+ align-items: center;
43216
+ gap: var(--swc-spacing-50);
43217
+ }
43218
+ .option .description,
43219
+ .option .notice {
43220
+ grid-column: 1 / -1;
43221
+ }
43222
+ .option .description,
43223
+ .lead,
43224
+ .empty {
43225
+ margin: 0;
43226
+ color: var(--swc-neutral-subdued-content-color-default);
43227
+ }
43228
+ .empty {
43229
+ padding: var(--swc-spacing-200) var(--swc-spacing-100);
43230
+ overflow-wrap: anywhere;
43231
+ }
43232
+ .picker .end {
43233
+ justify-content: flex-end;
43234
+ padding: var(--swc-spacing-300) var(--swc-spacing-500) var(--swc-spacing-400);
43235
+ }
43236
+ .step {
43237
+ display: grid;
43238
+ gap: var(--swc-spacing-200);
43239
+ padding: var(--swc-spacing-100) var(--swc-spacing-500) var(--swc-spacing-500);
43240
+ }
43241
+ .step .key.stacked {
43242
+ max-width: none;
43243
+ }
43244
+ .step sp-textfield {
43245
+ inline-size: 100%;
43246
+ }
43247
+ .step .buttons {
43248
+ margin-block-start: var(--swc-spacing-200);
43249
+ }
43250
+ .get-key {
43251
+ justify-self: start;
43252
+ font-size: var(--swc-font-size-75);
43253
+ }
43030
43254
  @container (max-width: 480px) {
43031
43255
  .page {
43032
43256
  padding-inline: var(--swc-spacing-300);
@@ -43057,7 +43281,8 @@ var SliccSettings = class extends ThemedElement {
43057
43281
  .state {
43058
43282
  grid-area: state;
43059
43283
  }
43060
- .account swc-button {
43284
+ .account swc-button,
43285
+ .account .actions {
43061
43286
  grid-area: action;
43062
43287
  }
43063
43288
  .key sp-textfield {
@@ -43077,6 +43302,7 @@ var SliccSettings = class extends ThemedElement {
43077
43302
  ...super.subscribe(model),
43078
43303
  model.settings.on("settings", settle2),
43079
43304
  model.settings.on("accounts", settle2),
43305
+ model.settings.on("providers", update),
43080
43306
  ...model.vault ? [model.vault.on("status", update)] : []
43081
43307
  ];
43082
43308
  }
@@ -43123,6 +43349,7 @@ var SliccSettings = class extends ThemedElement {
43123
43349
  try {
43124
43350
  await (fields ? settings?.connect(id, secret, fields) : secret === void 0 ? settings?.connect(id) : settings?.connect(id, secret));
43125
43351
  if (this.entering === id) this.entering = null;
43352
+ if (this.adding?.id === id && this.#added(id)?.status === "connected") this.#finish(id);
43126
43353
  } catch (error) {
43127
43354
  if (!this.#cancelled.has(id)) this.failure = { id, message: error.message };
43128
43355
  } finally {
@@ -43158,13 +43385,88 @@ var SliccSettings = class extends ThemedElement {
43158
43385
  this.#busy(id, false);
43159
43386
  return;
43160
43387
  }
43161
- await this.connect(id, void 0, fields);
43388
+ await this.#connectBases(account, void 0, fields, vault);
43389
+ }
43390
+ #baseOf(value) {
43391
+ const trimmed = value?.trim() ?? "";
43392
+ try {
43393
+ return new URL(trimmed).protocol === "https:" ? trimmed : void 0;
43394
+ } catch {
43395
+ return void 0;
43396
+ }
43397
+ }
43398
+ #oldBase(account, field) {
43399
+ return this.#baseOf(field.value) ?? this.#bases.get(`${account.id} ${field.name}`);
43400
+ }
43401
+ #baseChanges(account, fields) {
43402
+ return (account.fields ?? []).filter((field) => field.customBase).map((field) => ({
43403
+ field: field.name,
43404
+ url: this.#baseOf(fields?.[field.name]),
43405
+ old: this.#oldBase(account, field)
43406
+ })).filter((change) => change.url !== change.old);
43407
+ }
43408
+ #modelChange(name, changes2, from, to) {
43409
+ const entries = (key2) => changes2.flatMap((change) => change[key2] ? [{ name, url: change[key2] }] : []);
43410
+ const remove2 = entries(from);
43411
+ const add = entries(to);
43412
+ if (!remove2.length && !add.length) return null;
43413
+ return {
43414
+ ...remove2.length ? { remove: { model: remove2 } } : {},
43415
+ ...add.length ? { add: { model: add } } : {}
43416
+ };
43417
+ }
43418
+ async #connectBases(account, secret, fields, vault = null) {
43419
+ const model = this.model;
43420
+ const live = vault?.endpoints ? vault : null;
43421
+ const { id } = account;
43422
+ const name = recordName(account);
43423
+ const changes2 = this.#baseChanges(account, fields);
43424
+ const forward = live ? this.#modelChange(name, changes2, "old", "url") : null;
43425
+ if (forward) {
43426
+ this.#busy(id, true);
43427
+ try {
43428
+ await live?.endpoints?.(forward);
43429
+ } catch (error) {
43430
+ this.#busy(id, false);
43431
+ const field = changes2.find((change) => change.url) ?? changes2[0];
43432
+ if (this.model === model) this.#refuse(id, field.field, failureText(error));
43433
+ return;
43434
+ }
43435
+ if (this.model !== model) {
43436
+ this.#busy(id, false);
43437
+ return;
43438
+ }
43439
+ }
43440
+ await this.connect(id, secret, fields);
43441
+ if (this.failure?.id === id) {
43442
+ this.#placeFailure(account, fields);
43443
+ const back = forward ? this.#modelChange(name, changes2, "url", "old") : null;
43444
+ if (back) await live?.endpoints?.(back).catch(() => {
43445
+ });
43446
+ return;
43447
+ }
43448
+ for (const change of changes2) {
43449
+ const key2 = `${id} ${change.field}`;
43450
+ if (change.url) this.#bases.set(key2, change.url);
43451
+ else this.#bases.delete(key2);
43452
+ }
43453
+ }
43454
+ #placeFailure(account, fields) {
43455
+ const message = this.failure.message;
43456
+ const field = (account.fields ?? []).find(
43457
+ (candidate) => candidate.customBase && !!fields?.[candidate.name] && [candidate.name, candidate.label, "https", "url", "port", "userinfo"].some(
43458
+ (word) => new RegExp(`\\b${word.replace(/[^a-z0-9 ]/gi, "\\$&")}\\b`, "i").test(message)
43459
+ )
43460
+ );
43461
+ if (!field) return;
43462
+ this.failure = null;
43463
+ this.#refuse(account.id, field.name, message);
43162
43464
  }
43163
43465
  #refuse(id, field, message) {
43164
43466
  this.problem = { id, field, message };
43165
43467
  void this.updateComplete.then(
43166
43468
  () => this.renderRoot.querySelector(
43167
- field === "value" ? ".key input[data-field=value]" : `.key input[data-account-field="${field}"]`
43469
+ field === "value" ? ".key input[data-field=value]" : `.key [data-account-field="${field}"]`
43168
43470
  )?.focus()
43169
43471
  );
43170
43472
  }
@@ -43172,25 +43474,35 @@ var SliccSettings = class extends ThemedElement {
43172
43474
  if (!account.fields?.length) return void 0;
43173
43475
  const values = {};
43174
43476
  for (const field of account.fields) {
43175
- const input = form.querySelector(
43176
- `input[data-account-field="${field.name}"]`
43177
- );
43477
+ const input = form.querySelector(`[data-account-field="${field.name}"]`);
43178
43478
  const value = input.value.trim();
43179
43479
  if (value) values[field.name] = value;
43180
- else if (field.required) {
43480
+ else if (field.required && field.options?.length !== 0) {
43181
43481
  this.#refuse(account.id, field.name, `${field.label} can\u2019t be empty`);
43182
43482
  return null;
43183
43483
  }
43184
43484
  }
43185
43485
  return values;
43186
43486
  }
43487
+ #added(id) {
43488
+ return this.model?.settings.accounts().find((account) => account.id === id);
43489
+ }
43490
+ #offered() {
43491
+ return this.model?.settings.providers?.() ?? null;
43492
+ }
43187
43493
  #start(account) {
43188
43494
  if (account.auth !== "api-key") {
43189
43495
  void this.connect(account.id);
43190
43496
  return;
43191
43497
  }
43498
+ this.#enter(account);
43499
+ }
43500
+ #enter(account) {
43192
43501
  this.failure = null;
43193
43502
  this.problem = null;
43503
+ this.filled = new Set(
43504
+ (account.fields ?? []).filter((field) => field.options || field.value?.trim()).map((field) => field.name)
43505
+ );
43194
43506
  this.entering = account.id;
43195
43507
  void this.updateComplete.then(() => this.focusOn(".key sp-textfield", ".key input"));
43196
43508
  }
@@ -43198,6 +43510,7 @@ var SliccSettings = class extends ThemedElement {
43198
43510
  event2.preventDefault();
43199
43511
  const form = event2.currentTarget;
43200
43512
  this.problem = null;
43513
+ this.filled = new Set([...this.filled].filter((name) => name !== "value"));
43201
43514
  if (this.#vault(account)) {
43202
43515
  const value = take(form.querySelector("input[data-field=value]"));
43203
43516
  const fields2 = this.#fields(form, account);
@@ -43208,7 +43521,7 @@ var SliccSettings = class extends ThemedElement {
43208
43521
  const secret = field.value.trim();
43209
43522
  field.value = "";
43210
43523
  const fields = this.#fields(form, account);
43211
- if (secret && fields !== null) void this.connect(account.id, secret, fields);
43524
+ if (secret && fields !== null) void this.#connectBases(account, secret, fields);
43212
43525
  }
43213
43526
  #submitOnEnter(event2) {
43214
43527
  const target2 = event2.target;
@@ -43219,7 +43532,7 @@ var SliccSettings = class extends ThemedElement {
43219
43532
  #send(event2) {
43220
43533
  event2.currentTarget.closest("form")?.requestSubmit();
43221
43534
  }
43222
- #enter(event2) {
43535
+ #enterKey(event2) {
43223
43536
  if (event2.key === "Enter") this.#send(event2);
43224
43537
  }
43225
43538
  #cancel() {
@@ -43244,17 +43557,64 @@ var SliccSettings = class extends ThemedElement {
43244
43557
  }
43245
43558
  #extra(account) {
43246
43559
  return (account.fields ?? []).map(
43247
- (field) => b`<label class="field"><span>${field.label}</span><input
43560
+ (field) => b`<label class="field">
43561
+ <span>${field.label}</span>${this.#control(account, field)}
43562
+ </label>
43563
+ ${field.help ? b`<p class="hint" id=${`help-${field.name}`}>${field.help}</p>` : A}
43564
+ ${this.#problem(account, field.name)}`
43565
+ );
43566
+ }
43567
+ #describedBy(account, field) {
43568
+ const problem = this.#described(account, field.name);
43569
+ const ids = [problem === A ? "" : problem, field.help ? `help-${field.name}` : ""];
43570
+ return ids.filter(Boolean).join(" ") || A;
43571
+ }
43572
+ #control(account, field) {
43573
+ if (field.options) {
43574
+ const chosen = field.value ?? field.options[0]?.value;
43575
+ return b`<select
43576
+ data-account-field=${field.name}
43577
+ ?disabled=${!field.options.length}
43578
+ aria-required=${field.required ? "true" : A}
43579
+ aria-invalid=${this.#invalid(account, field.name)}
43580
+ aria-describedby=${this.#describedBy(account, field)}
43581
+ >
43582
+ ${field.options.map((option2) => b`<option value=${option2.value} ?selected=${option2.value === chosen}>${option2.label}</option>`)}
43583
+ </select>`;
43584
+ }
43585
+ return b`<input
43248
43586
  type="text"
43249
43587
  autocomplete="off"
43250
43588
  spellcheck="false"
43251
43589
  data-account-field=${field.name}
43590
+ value=${field.value ?? A}
43591
+ placeholder=${field.placeholder ?? A}
43252
43592
  aria-required=${field.required ? "true" : A}
43253
43593
  aria-invalid=${this.#invalid(account, field.name)}
43254
- aria-describedby=${this.#described(account, field.name)}
43255
- /></label>
43256
- ${this.#problem(account, field.name)}`
43257
- );
43594
+ aria-describedby=${this.#describedBy(account, field)}
43595
+ />`;
43596
+ }
43597
+ #getKey(account) {
43598
+ if (!account.keyUrl) return A;
43599
+ return b`<a class="swc-Link get-key" href=${account.keyUrl} target="_blank" rel="noopener noreferrer" data-action="get-key">Get a key</a>`;
43600
+ }
43601
+ #leave(account) {
43602
+ const adding = this.adding?.id === account.id;
43603
+ return b`<swc-button size="m" variant="secondary" data-action=${adding ? "back" : "cancel"} @click=${() => adding ? this.#back() : this.#cancel()}>${adding ? "Back" : "Cancel"}</swc-button>`;
43604
+ }
43605
+ #saveLabel(account) {
43606
+ return this.adding?.id === account.id ? "Connect" : "Save";
43607
+ }
43608
+ #ready(account) {
43609
+ if (!this.#offered()) return true;
43610
+ const required = (account.fields ?? []).filter((field) => field.required);
43611
+ return ["value", ...required.map((field) => field.name)].every((name) => this.filled.has(name));
43612
+ }
43613
+ #fill(event2) {
43614
+ const input = event2.target;
43615
+ const name = input.dataset.accountField ?? "value";
43616
+ const rest = [...this.filled].filter((candidate) => candidate !== name);
43617
+ this.filled = new Set(input.value.trim() ? [...rest, name] : rest);
43258
43618
  }
43259
43619
  #unlock(vault) {
43260
43620
  return b`<div class="locked" data-locked>
@@ -43274,7 +43634,7 @@ var SliccSettings = class extends ThemedElement {
43274
43634
  const busy = this.pending.has(account.id);
43275
43635
  const locked = !!vault.status()?.locked;
43276
43636
  const name = secretNames[account.secret ?? "api-key"];
43277
- return b`<form class="key stacked" data-id=${account.id} data-store="vault" @submit=${(event2) => this.#submit(event2, account)} @keydown=${(event2) => this.#submitOnEnter(event2)}>
43637
+ return b`<form class="key stacked" data-id=${account.id} data-store="vault" @submit=${(event2) => this.#submit(event2, account)} @keydown=${(event2) => this.#submitOnEnter(event2)} @input=${(event2) => this.#fill(event2)}>
43278
43638
  <label class="field"><span>${account.provider} ${name}</span><input
43279
43639
  type="password"
43280
43640
  autocomplete="off"
@@ -43285,11 +43645,12 @@ var SliccSettings = class extends ThemedElement {
43285
43645
  aria-describedby=${this.#described(account, "value")}
43286
43646
  /></label>
43287
43647
  ${this.#problem(account, "value")}
43288
- ${this.#extra(account)}
43289
43648
  ${locked ? this.#unlock(vault) : b`<p class="hint">Saved in the vault</p>`}
43649
+ ${this.#getKey(account)}
43650
+ ${this.#extra(account)}
43290
43651
  <div class="buttons">
43291
- <swc-button size="m" variant="accent" data-action="save" ?pending=${busy} ?disabled=${locked} @click=${(event2) => this.#send(event2)}>Save</swc-button>
43292
- <swc-button size="m" variant="secondary" data-action="cancel" @click=${() => this.#cancel()}>Cancel</swc-button>
43652
+ <swc-button size="m" variant="accent" data-action="save" ?pending=${busy} ?disabled=${locked || !busy && !this.#ready(account)} @click=${(event2) => this.#send(event2)}>${this.#saveLabel(account)}</swc-button>
43653
+ ${this.#leave(account)}
43293
43654
  </div>
43294
43655
  </form>`;
43295
43656
  }
@@ -43298,23 +43659,24 @@ var SliccSettings = class extends ThemedElement {
43298
43659
  const vault = this.#vault(account);
43299
43660
  if (vault) return this.#kept(account, vault);
43300
43661
  const busy = this.pending.has(account.id);
43301
- const extra = account.fields?.length;
43302
- return b`<form class=${extra ? "key stacked" : "key"} data-id=${account.id} @submit=${(event2) => this.#submit(event2, account)} @keydown=${(event2) => this.#submitOnEnter(event2)}>
43662
+ const extra = account.fields?.length || account.keyUrl;
43663
+ return b`<form class=${extra ? "key stacked" : "key"} data-id=${account.id} @submit=${(event2) => this.#submit(event2, account)} @keydown=${(event2) => this.#submitOnEnter(event2)} @input=${(event2) => this.#fill(event2)}>
43303
43664
  <sp-textfield
43304
43665
  size="m"
43305
43666
  type="password"
43306
43667
  autocomplete="off"
43307
43668
  label=${`${account.provider} ${secretNames[account.secret ?? "api-key"]}`}
43308
43669
  placeholder=${`Paste the ${secretNames[account.secret ?? "api-key"]}`}
43309
- @keydown=${(event2) => this.#enter(event2)}
43670
+ @keydown=${(event2) => this.#enterKey(event2)}
43310
43671
  ></sp-textfield>
43672
+ ${this.#getKey(account)}
43311
43673
  ${extra ? this.#extra(account) : A}
43312
- ${extra ? b`<div class="buttons">${this.#buttons(busy)}</div>` : this.#buttons(busy)}
43674
+ ${extra ? b`<div class="buttons">${this.#buttons(account, busy)}</div>` : this.#buttons(account, busy)}
43313
43675
  </form>`;
43314
43676
  }
43315
- #buttons(busy) {
43316
- return b`<swc-button size="m" variant="accent" data-action="save" ?pending=${busy} @click=${(event2) => this.#send(event2)}>Save</swc-button>
43317
- <swc-button size="m" variant="secondary" data-action="cancel" @click=${() => this.#cancel()}>Cancel</swc-button>`;
43677
+ #buttons(account, busy) {
43678
+ return b`<swc-button size="m" variant="accent" data-action="save" ?pending=${busy} ?disabled=${!busy && !this.#ready(account)} @click=${(event2) => this.#send(event2)}>${this.#saveLabel(account)}</swc-button>
43679
+ ${this.#leave(account)}`;
43318
43680
  }
43319
43681
  #inVault(account) {
43320
43682
  const vault = this.model?.vault;
@@ -43366,7 +43728,8 @@ var SliccSettings = class extends ThemedElement {
43366
43728
  get busyReason() {
43367
43729
  const accounts = this.model?.settings.accounts() ?? [];
43368
43730
  if (accounts.some((account) => this.#signingIn(account))) return "signing in";
43369
- return this.entering === null ? null : "a key is being entered";
43731
+ if (this.entering !== null) return "a key is being entered";
43732
+ return this.picking ? "a provider is being added" : null;
43370
43733
  }
43371
43734
  #signingIn(account) {
43372
43735
  return account.status === "signing-in" || account.auth !== "api-key" && account.status !== "connected" && this.pending.has(account.id);
@@ -43423,10 +43786,266 @@ var SliccSettings = class extends ThemedElement {
43423
43786
  <span class="provider">${account.provider}</span>
43424
43787
  <span class="identity">${account.identity}${this.#inVault(account) ? b`<span class="kept" data-store="vault">${account.identity ? " \xB7 " : ""}${kept2(account)}</span>` : A}</span>
43425
43788
  <swc-status-light class="state" variant=${variant}>${label}</swc-status-light>
43426
- ${this.#action(account)}
43789
+ ${this.#removable(account) ? b`<div class="actions">${this.#action(account)}${this.#removeButton(account)}</div>` : this.#action(account)}
43427
43790
  </div>
43428
- ${this.#key(account)}
43429
- ${failure2}`;
43791
+ ${this.#offered() ? this.#rowNotes(account) : A}
43792
+ ${this.adding?.id === account.id ? A : b`${this.#key(account)}${failure2}`}`;
43793
+ }
43794
+ #removable(account) {
43795
+ return !!this.#offered() && !!this.model?.settings.remove && account.group !== "mcp";
43796
+ }
43797
+ #removeButton(account) {
43798
+ const id = `remove-${account.id}`;
43799
+ const label = `Remove ${account.provider}`;
43800
+ return b`<swc-action-button
43801
+ id=${id}
43802
+ size="m"
43803
+ quiet
43804
+ data-action="remove-account"
43805
+ accessible-label=${label}
43806
+ ?disabled=${this.pending.has(account.id)}
43807
+ @click=${() => this.#remove(account)}
43808
+ ><swc-icon-delete slot="icon"></swc-icon-delete></swc-action-button
43809
+ ><swc-tooltip for=${id} placement="top">${label}</swc-tooltip>`;
43810
+ }
43811
+ async #remove(account) {
43812
+ const model = this.model;
43813
+ const body4 = removeBodies[account.auth ?? ""] ?? "Its key is deleted from the vault. Models from it stop working.";
43814
+ const title = `Remove ${account.provider}?`;
43815
+ if (!await confirm({ title, body: body4, action: "Remove" })) return;
43816
+ if (this.model !== model) return;
43817
+ clearValues(this.renderRoot);
43818
+ if (this.entering === account.id) this.entering = null;
43819
+ const vault = model.vault;
43820
+ const keyed = account.store === "vault" && account.auth === "api-key" && !!vault?.remove;
43821
+ if (keyed && !await this.#forget(account, vault)) return;
43822
+ if (this.model !== model) return;
43823
+ this.#busy(account.id, true);
43824
+ this.failure = null;
43825
+ try {
43826
+ await model.settings.remove?.(account.id);
43827
+ } catch (error) {
43828
+ if (this.model === model) this.failure = { id: account.id, message: failureText(error) };
43829
+ return;
43830
+ } finally {
43831
+ this.#busy(account.id, false);
43832
+ }
43833
+ this.focusOn("[data-action=add-provider]");
43834
+ for (const field of account.fields ?? []) this.#bases.delete(`${account.id} ${field.name}`);
43835
+ }
43836
+ #notice(needs, size4 = "long") {
43837
+ if (needs !== "cors-free-transport") return A;
43838
+ return b`<p class="notice" data-notice=${needs}><swc-icon-alert-triangle size="s" aria-hidden="true"></swc-icon-alert-triangle><span>${corsNotice[size4]}</span></p>`;
43839
+ }
43840
+ #signingNote(account) {
43841
+ const key2 = `settings:${account.id}`;
43842
+ if (account.auth !== "oauth" || !this.#signingIn(account)) {
43843
+ this.#waits.done(key2);
43844
+ return A;
43845
+ }
43846
+ return b`<p class="hint" role="status" data-signing>${signInText(this.#waits.waited(key2))}</p>`;
43847
+ }
43848
+ #rowNotes(account) {
43849
+ const notice = this.#notice(account.needs);
43850
+ const signing = this.adding?.id === account.id ? A : this.#signingNote(account);
43851
+ if (notice === A && signing === A) return A;
43852
+ return b`<div class="notes">${notice}${signing}</div>`;
43853
+ }
43854
+ #openPicker() {
43855
+ this.query = "";
43856
+ this.adding = null;
43857
+ this.picking = true;
43858
+ }
43859
+ updated(changed) {
43860
+ super.updated(changed);
43861
+ const dialog = this.#dialog;
43862
+ if (!changed.has("picking") || !dialog || dialog.open) return;
43863
+ dialog.showModal();
43864
+ const field = this.renderRoot.querySelector("sp-search");
43865
+ void field.updateComplete.then(() => field.focus());
43866
+ }
43867
+ get #dialog() {
43868
+ return this.renderRoot.querySelector("dialog.picker");
43869
+ }
43870
+ #closePicker() {
43871
+ this.#dialog?.close();
43872
+ }
43873
+ #closed() {
43874
+ const id = this.adding?.id;
43875
+ if (id !== void 0 && this.entering === id) {
43876
+ clearValues(this.renderRoot);
43877
+ this.entering = null;
43878
+ this.problem = null;
43879
+ }
43880
+ if (id !== void 0 && this.failure?.id === id) this.failure = null;
43881
+ this.picking = false;
43882
+ this.adding = null;
43883
+ this.query = "";
43884
+ const landing = this.#landing;
43885
+ this.#landing = null;
43886
+ void this.updateComplete.then(() => {
43887
+ const root2 = this.renderRoot;
43888
+ const row = landing ? root2.querySelector(`.account[data-id="${CSS.escape(landing)}"] swc-button`) : null;
43889
+ (row ?? root2.querySelector("[data-action=add-provider]"))?.focus();
43890
+ });
43891
+ }
43892
+ #finish(id) {
43893
+ this.#landing = id;
43894
+ this.#closePicker();
43895
+ }
43896
+ #choose(option2) {
43897
+ if (this.#added(option2.id)) return;
43898
+ this.adding = option2;
43899
+ this.failure = null;
43900
+ if (option2.auth === "api-key" || option2.auth === "fields") {
43901
+ this.#enter(draft(option2));
43902
+ return;
43903
+ }
43904
+ void this.updateComplete.then(
43905
+ () => this.focusOn("[data-step] [data-action=sign-in]", "[data-step] [data-action=connect]")
43906
+ );
43907
+ }
43908
+ #back() {
43909
+ const id = this.adding.id;
43910
+ clearValues(this.renderRoot);
43911
+ if (this.entering === id) this.entering = null;
43912
+ if (this.failure?.id === id) this.failure = null;
43913
+ this.problem = null;
43914
+ this.adding = null;
43915
+ void this.updateComplete.then(
43916
+ () => this.renderRoot.querySelector(`.option[data-provider="${CSS.escape(id)}"]`)?.focus()
43917
+ );
43918
+ }
43919
+ #options() {
43920
+ return [...this.renderRoot.querySelectorAll(".option")];
43921
+ }
43922
+ #pickerKey(event2) {
43923
+ if (event2.key !== "Escape") return;
43924
+ event2.preventDefault();
43925
+ this.#closePicker();
43926
+ }
43927
+ #searchKey(event2) {
43928
+ if (event2.key === "ArrowDown") {
43929
+ event2.preventDefault();
43930
+ this.#options()[0]?.focus();
43931
+ return;
43932
+ }
43933
+ if (event2.key !== "Enter") return;
43934
+ event2.preventDefault();
43935
+ const first2 = matchProviders(this.#offered() ?? [], this.query).find(
43936
+ (option2) => !this.#added(option2.id)
43937
+ );
43938
+ if (first2) this.#choose(first2);
43939
+ }
43940
+ #optionKey(event2, option2) {
43941
+ const options = this.#options();
43942
+ const at = options.indexOf(event2.currentTarget);
43943
+ const moves = {
43944
+ ArrowDown: Math.min(at + 1, options.length - 1),
43945
+ ArrowUp: at - 1,
43946
+ Home: 0,
43947
+ End: options.length - 1
43948
+ };
43949
+ const typed3 = event2.key.length === 1 && !event2.ctrlKey && !event2.metaKey && !event2.altKey;
43950
+ if (event2.key in moves) {
43951
+ const to = moves[event2.key];
43952
+ if (to < 0) this.focusOn("sp-search");
43953
+ else options[to].focus();
43954
+ } else if (event2.key === "Enter" || event2.key === " ") this.#choose(option2);
43955
+ else if (typed3 || event2.key === "Backspace") {
43956
+ this.query = typed3 ? this.query + event2.key : this.query.slice(0, -1);
43957
+ this.focusOn("sp-search");
43958
+ } else return;
43959
+ event2.preventDefault();
43960
+ }
43961
+ #option(option2) {
43962
+ const added = !!this.#added(option2.id);
43963
+ return b`<div
43964
+ class="option"
43965
+ role="option"
43966
+ tabindex="-1"
43967
+ data-provider=${option2.id}
43968
+ data-auth=${option2.auth}
43969
+ aria-disabled=${added ? "true" : "false"}
43970
+ @click=${() => this.#choose(option2)}
43971
+ @keydown=${(event2) => this.#optionKey(event2, option2)}
43972
+ >
43973
+ <span class="name">${option2.name}</span>
43974
+ <span class="tag">${added ? b`<span class="added" data-added><swc-icon-checkmark size="s" aria-hidden="true"></swc-icon-checkmark>Added</span>` : authHints[option2.auth]}</span>
43975
+ ${option2.description ? b`<span class="description">${option2.description}</span>` : A}
43976
+ ${this.#notice(option2.needs, "short")}
43977
+ </div>`;
43978
+ }
43979
+ #pickStep(offered) {
43980
+ const matches2 = matchProviders(offered, this.query);
43981
+ return b`<div class="find">${search({
43982
+ label: "Search providers",
43983
+ value: this.query,
43984
+ input: (value) => {
43985
+ this.query = value;
43986
+ },
43987
+ keydown: (event2) => this.#searchKey(event2)
43988
+ })}</div>
43989
+ <div class="results">${matches2.length ? b`<div class="options" role="listbox" aria-label="Providers">${matches2.map((option2) => this.#option(option2))}</div>` : b`<p class="empty" role="status">No provider matches “${this.query.trim()}”.</p>`}</div>
43990
+ <div class="buttons end">
43991
+ <swc-button size="m" variant="secondary" data-action="close" @click=${() => this.#closePicker()}>Cancel</swc-button>
43992
+ </div>`;
43993
+ }
43994
+ #signInStep(account) {
43995
+ const signing = this.#signingIn(account);
43996
+ const cancel = signing && !!this.model?.settings.cancel;
43997
+ return b`<p class="hint">Sign in to ${account.provider} in a new window.</p>
43998
+ ${signing ? this.#signingNote(account) : A}
43999
+ <div class="buttons">
44000
+ ${cancel ? b`<swc-button size="m" variant="secondary" data-action="cancel-sign-in" @click=${() => this.#stopSigningIn(account)}>Cancel</swc-button>` : A}
44001
+ <swc-button size="m" variant="accent" data-action="sign-in" ?pending=${signing} @click=${() => this.connect(account.id)}>${signing ? "Signing in\u2026" : "Sign in"}</swc-button>
44002
+ ${signing ? A : this.#leave(account)}
44003
+ </div>`;
44004
+ }
44005
+ #localStep(account) {
44006
+ return b`<p class="hint">SLICC connects to ${account.provider} on this device.</p>
44007
+ <div class="buttons">
44008
+ <swc-button size="m" variant="accent" data-action="connect" ?pending=${this.pending.has(account.id)} @click=${() => this.connect(account.id)}>Connect</swc-button>
44009
+ ${this.#leave(account)}
44010
+ </div>`;
44011
+ }
44012
+ #connectStep(option2) {
44013
+ const account = this.#added(option2.id) ?? draft(option2);
44014
+ const step = option2.auth === "oauth" ? this.#signInStep(account) : option2.auth === "local" ? this.#localStep(account) : this.#key(account);
44015
+ return b`<div class="step" data-step=${option2.auth} data-provider=${option2.id}>
44016
+ ${option2.description ? b`<p class="lead">${option2.description}</p>` : A}
44017
+ ${this.#notice(option2.needs)}
44018
+ ${step}
44019
+ ${this.failure?.id === option2.id ? b`<div class="failure" role="alert"><swc-icon-alert-triangle size="s" aria-hidden="true"></swc-icon-alert-triangle><span>${this.failure.message}</span></div>` : A}
44020
+ </div>`;
44021
+ }
44022
+ #picker(offered) {
44023
+ if (!this.picking) return A;
44024
+ const adding = this.adding;
44025
+ return b`<dialog class="picker" aria-labelledby="picker-title" data-picker=${adding ? "connect" : "pick"} @keydown=${(event2) => this.#pickerKey(event2)} @close=${() => this.#closed()}>
44026
+ <div class="head">
44027
+ <h2 id="picker-title">${adding ? `Add ${adding.name}` : "Add a provider"}</h2>
44028
+ <swc-close-button size="m" data-action="close" accessible-label="Close" @click=${() => this.#closePicker()}></swc-close-button>
44029
+ </div>
44030
+ ${adding ? this.#connectStep(adding) : this.#pickStep(offered)}
44031
+ </dialog>`;
44032
+ }
44033
+ #providerList(accounts) {
44034
+ const offered = this.#offered();
44035
+ if (!offered)
44036
+ return b`<div class="accounts">${accounts.map((account) => this.#account(account))}</div>`;
44037
+ const sorted = [...accounts].sort((a23, b13) => listOrder[a23.status] - listOrder[b13.status]);
44038
+ return b`<div class="accounts" data-group="providers">${c(
44039
+ sorted,
44040
+ (account) => account.id,
44041
+ (account) => this.#account(account)
44042
+ )}</div>
44043
+ <div class="add">
44044
+ <swc-action-button size="m" quiet data-action="add-provider" @click=${() => this.#openPicker()}>
44045
+ <swc-icon-add slot="icon"></swc-icon-add>Add a provider
44046
+ </swc-action-button>
44047
+ </div>
44048
+ ${this.#picker(offered)}`;
43430
44049
  }
43431
44050
  render() {
43432
44051
  const settings = this.model?.settings;
@@ -43482,7 +44101,7 @@ var SliccSettings = class extends ThemedElement {
43482
44101
  </div>
43483
44102
  </div>
43484
44103
  <h2 data-section="accounts">Accounts</h2>
43485
- <div class="accounts">${providers.map((account) => this.#account(account))}</div>
44104
+ ${this.#providerList(providers)}
43486
44105
  ${servers.length ? b`<h2>MCP servers</h2>
43487
44106
  <div class="accounts" data-group="mcp">${servers.map((account) => this.#account(account))}</div>` : A}
43488
44107
  ${this.model?.vault ? b`<slicc-secrets .model=${this.model}></slicc-secrets>` : A}