@ai-ecoverse/slicc-spectrum 2.5.0 → 2.7.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
@@ -24607,7 +24607,7 @@ function createEditSessionFile(file) {
24607
24607
  return editSessionFile;
24608
24608
  }
24609
24609
  var instanceId2 = -1;
24610
- var File = class {
24610
+ var File2 = class {
24611
24611
  options;
24612
24612
  workerManager;
24613
24613
  requestOwnerRender;
@@ -35643,6 +35643,7 @@ var SliccComposer = class extends ThemedElement {
35643
35643
  entry: { state: true },
35644
35644
  failure: { state: true },
35645
35645
  reattach: { state: true },
35646
+ recording: { state: true },
35646
35647
  agent: {}
35647
35648
  };
35648
35649
  #drafts = /* @__PURE__ */ new Map();
@@ -35651,6 +35652,7 @@ var SliccComposer = class extends ThemedElement {
35651
35652
  #paths = [];
35652
35653
  #agent = "";
35653
35654
  #next = 1;
35655
+ #session = null;
35654
35656
  #abandon;
35655
35657
  #recognition = null;
35656
35658
  #erase;
@@ -35678,6 +35680,7 @@ var SliccComposer = class extends ThemedElement {
35678
35680
  this.entry = null;
35679
35681
  this.failure = null;
35680
35682
  this.reattach = null;
35683
+ this.recording = null;
35681
35684
  this.agent = "";
35682
35685
  }
35683
35686
  static styles = [
@@ -36223,23 +36226,98 @@ ${this.value}` : text4;
36223
36226
  release(this.attachments.filter((attachment2) => attachment2.id === id));
36224
36227
  this.attachments = this.attachments.filter((attachment2) => attachment2.id !== id);
36225
36228
  }
36226
- async screenshot() {
36227
- const browser = this.model?.browser;
36228
- const tab = browser?.list().find((candidate) => candidate.id === browser.active());
36229
- if (!(browser && tab)) return;
36230
- const url = await browser.screenshot(tab.id);
36229
+ #failAttach(name, error, mimeType = "application/octet-stream") {
36231
36230
  this.attachments = [
36232
36231
  ...this.attachments,
36233
36232
  {
36234
36233
  id: this.#id(),
36235
- name: `${tab.title}.png`,
36236
- kind: "image",
36237
- mimeType: "image/png",
36238
- size: url.length,
36239
- url
36234
+ name,
36235
+ kind: "file",
36236
+ mimeType,
36237
+ size: 0,
36238
+ error
36240
36239
  }
36241
36240
  ];
36242
36241
  }
36242
+ #switchOff(key) {
36243
+ const found = this.model?.switches?.list().find((item2) => item2.key === key);
36244
+ return !!found && !found.on;
36245
+ }
36246
+ #captureError(error, fallback, switchKey) {
36247
+ if (!(error instanceof Error)) return fallback;
36248
+ if (error.name === "AbortError") return "";
36249
+ if (error.name === "NotAllowedError" && switchKey && this.#switchOff(switchKey)) {
36250
+ const labels = {
36251
+ screen: "Screen capture",
36252
+ camera: "Camera",
36253
+ microphone: "Microphone"
36254
+ };
36255
+ return `${labels[switchKey]} is switched off. Turn it on in Settings, Capture and devices.`;
36256
+ }
36257
+ if (error.name === "NotAllowedError") return error.message || fallback;
36258
+ if (error.name === "NotSupportedError")
36259
+ return error.message || "Recording isn\u2019t supported here";
36260
+ return error.message || fallback;
36261
+ }
36262
+ #errorDetail(error) {
36263
+ return error.replace(" is switched off.", " is off.").replace(", Capture and devices.", ".");
36264
+ }
36265
+ async screenshot() {
36266
+ const capture = this.model?.capture;
36267
+ if (capture?.still) {
36268
+ try {
36269
+ const shot = await capture.still();
36270
+ await this.addFiles([new File([shot.blob], shot.name, { type: shot.mimeType })]);
36271
+ } catch (error) {
36272
+ const message = this.#captureError(error, "Screen capture was denied", "screen");
36273
+ if (message) this.#failAttach("screenshot.png", message, "image/png");
36274
+ }
36275
+ return;
36276
+ }
36277
+ const browser = this.model?.browser;
36278
+ const tab = browser?.list().find((candidate) => candidate.id === browser.active());
36279
+ if (!(browser && tab)) {
36280
+ this.#failAttach("screenshot.png", "No screen or browser tab to screenshot", "image/png");
36281
+ return;
36282
+ }
36283
+ try {
36284
+ const url = await browser.screenshot(tab.id);
36285
+ this.attachments = [
36286
+ ...this.attachments,
36287
+ {
36288
+ id: this.#id(),
36289
+ name: `${tab.title}.png`,
36290
+ kind: "image",
36291
+ mimeType: "image/png",
36292
+ size: url.length,
36293
+ url
36294
+ }
36295
+ ];
36296
+ } catch (error) {
36297
+ const message = this.#captureError(error, "Could not take a screenshot");
36298
+ if (message) this.#failAttach("screenshot.png", message, "image/png");
36299
+ }
36300
+ }
36301
+ async record(kind) {
36302
+ const capture = this.model?.capture;
36303
+ if (!capture || this.#session) return;
36304
+ try {
36305
+ const session2 = await capture.record(kind);
36306
+ this.#session = session2;
36307
+ this.recording = kind;
36308
+ const take2 = await session2.finished;
36309
+ this.#session = null;
36310
+ this.recording = null;
36311
+ await this.addFiles([new File([take2.blob], take2.name, { type: take2.mimeType })]);
36312
+ } catch (error) {
36313
+ this.#session = null;
36314
+ this.recording = null;
36315
+ const mic = kind === "audio";
36316
+ const fallback = mic ? "Microphone access was denied" : "Camera access was denied";
36317
+ const message = this.#captureError(error, fallback, mic ? "microphone" : "camera");
36318
+ if (message) this.#failAttach("recording.webm", message, mic ? "audio/webm" : "video/webm");
36319
+ }
36320
+ }
36243
36321
  async attachPath(path2) {
36244
36322
  const text4 = await this.model?.files.read(path2).catch(() => null);
36245
36323
  const name = path2.slice(path2.lastIndexOf("/") + 1);
@@ -36613,6 +36691,8 @@ ${this.value}` : text4;
36613
36691
  if (value === "upload")
36614
36692
  this.renderRoot.querySelector("input[type=file]")?.click();
36615
36693
  else if (value === "screenshot") void this.screenshot();
36694
+ else if (value === "record-audio") void this.record("audio");
36695
+ else if (value === "record-video") void this.record("video");
36616
36696
  else {
36617
36697
  this.openPopup(value);
36618
36698
  void this.updateComplete.then(
@@ -36660,7 +36740,7 @@ ${this.value}` : text4;
36660
36740
  return b`<span slot="thumbnail" class="thumb" ?data-error=${!!attachment2.error} aria-hidden="true">${icon}</span>`;
36661
36741
  }
36662
36742
  #attachment(attachment2) {
36663
- const detail = attachment2.error ?? (attachment2.kind === "secret" ? "Secret" : size3(attachment2.size));
36743
+ const detail = attachment2.error ? this.#errorDetail(attachment2.error) : attachment2.kind === "secret" ? "Secret" : size3(attachment2.size);
36664
36744
  return b`<swc-upload-attachment
36665
36745
  slot="attachment"
36666
36746
  type="card"
@@ -36668,12 +36748,12 @@ ${this.value}` : text4;
36668
36748
  data-id=${attachment2.id}
36669
36749
  data-kind=${attachment2.kind}
36670
36750
  ?data-error=${!!attachment2.error}
36671
- title=${attachment2.path ?? attachment2.name}
36751
+ title=${attachment2.error ?? attachment2.path ?? attachment2.name}
36672
36752
  @swc-upload-attachment-dismiss=${() => this.detach(attachment2.id)}
36673
36753
  >
36674
36754
  ${this.#thumbnail(attachment2)}
36675
36755
  <span slot="title">${attachment2.name}</span>
36676
- <span slot="subtitle" class=${attachment2.error ? "failed" : ""}>${detail}</span>
36756
+ <span slot="subtitle" class=${attachment2.error ? "failed" : ""} title=${attachment2.error ?? A}>${detail}</span>
36677
36757
  </swc-upload-attachment>`;
36678
36758
  }
36679
36759
  #popup() {
@@ -36946,7 +37026,9 @@ ${this.value}` : text4;
36946
37026
  <swc-popover placement="top-start" accessible-label="Add to the message" @keydown=${this.#menuKey} @swc-after-open=${() => this.renderRoot.querySelector("sp-menu")?.focus()}>
36947
37027
  <sp-menu label="Add to the message" @change=${this.#add}>
36948
37028
  <sp-menu-item value="upload">Upload from this computer</sp-menu-item>
36949
- <sp-menu-item value="screenshot">Take a screenshot of the browser tab</sp-menu-item>
37029
+ <sp-menu-item value="screenshot" ?disabled=${!!this.recording}>Take a screenshot</sp-menu-item>
37030
+ ${this.model?.capture ? b`<sp-menu-item value="record-audio" data-action="record-audio" ?disabled=${!!this.recording}>Record audio</sp-menu-item>` : A}
37031
+ ${this.model?.capture?.kinds?.includes("video") ? b`<sp-menu-item value="record-video" data-action="record-video" ?disabled=${!!this.recording}>Record video</sp-menu-item>` : A}
36950
37032
  <sp-menu-item value="file">Attach a file from SLICC</sp-menu-item>
36951
37033
  <sp-menu-item value="secret">Share a secret</sp-menu-item>
36952
37034
  ${talk ? b`<sp-menu-divider></sp-menu-divider>
@@ -42703,7 +42785,8 @@ var controlBadges = {
42703
42785
  var accessLabels = {
42704
42786
  view: "View",
42705
42787
  control: "Control",
42706
- terminal: "Terminal"
42788
+ terminal: "Terminal",
42789
+ browser: "Browser"
42707
42790
  };
42708
42791
  var accessKeys = Object.keys(accessLabels);
42709
42792
  function accessShown(access) {
@@ -42731,6 +42814,10 @@ function accessConfirm(device, key) {
42731
42814
  terminal: [
42732
42815
  `Give ${name} a root shell?`,
42733
42816
  "Terminal lets it open shells on this SLICC as root: it can run any command and read or change every file."
42817
+ ],
42818
+ browser: [
42819
+ `Let ${name} drive this browser?`,
42820
+ "It can open, read and control the tabs in this browser, as you, with your signed-in sites. This SLICC\u2019s own pages and the vault stay out of its reach."
42734
42821
  ]
42735
42822
  }[key];
42736
42823
  return { title: title2, body: `${body2} ${again}`, action: "Allow", variant: "confirmation" };
@@ -54176,7 +54263,7 @@ var SliccLive = class extends ModelElement {
54176
54263
  // src/app/lucide.ts
54177
54264
  var library = null;
54178
54265
  function load2() {
54179
- library ??= import("./chunk-NMHCUT6N.js");
54266
+ library ??= import("./chunk-3MP6GPRK.js");
54180
54267
  return library;
54181
54268
  }
54182
54269
  function pascal(name) {
@@ -55765,7 +55852,7 @@ var SliccSettings = class extends ThemedElement {
55765
55852
  (item2) => b`<sp-switch .checked=${l(item2.on)} data-id=${item2.key} @change=${(event2) => port.set(item2.key, this.#checked(event2))}>${item2.label}</sp-switch>`
55766
55853
  )}
55767
55854
  </div>
55768
- <p class="hint">Programs can capture only what’s on here. Turning one off stops what’s live under it.</p>
55855
+ <p class="hint">Programs and linked devices can use only what’s on here. Turning one off stops what’s live under it.</p>
55769
55856
  </div>
55770
55857
  </div>
55771
55858
  </div>`;
@@ -58422,7 +58509,7 @@ var SliccCodeView = class extends PierreView {
58422
58509
  }
58423
58510
  draw(container) {
58424
58511
  if (this.#contents === null) return this.empty(container, "Nothing to show");
58425
- const viewer = new File(this.options());
58512
+ const viewer = new File2(this.options());
58426
58513
  viewer.render({
58427
58514
  file: { name: this.path, contents: this.#contents },
58428
58515
  containerWrapper: container