@coffer-org/plugin-webchat 2.3.0 → 3.1.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.
@@ -527,6 +527,21 @@ var ChevronDown = createLucideIcon("chevron-down", [["path", {
527
527
  * This source code is licensed under the ISC license.
528
528
  * See the LICENSE file in the root directory of this source tree.
529
529
  */
530
+ var Clock = createLucideIcon("clock", [["circle", {
531
+ cx: "12",
532
+ cy: "12",
533
+ r: "10",
534
+ key: "1mglay"
535
+ }], ["path", {
536
+ d: "M12 6v6l4 2",
537
+ key: "mmk7yg"
538
+ }]]);
539
+ /**
540
+ * @license lucide-react v1.24.0 - ISC
541
+ *
542
+ * This source code is licensed under the ISC license.
543
+ * See the LICENSE file in the root directory of this source tree.
544
+ */
530
545
  var FileText = createLucideIcon("file-text", [
531
546
  ["path", {
532
547
  d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",
@@ -718,6 +733,16 @@ var Square = createLucideIcon("square", [["rect", {
718
733
  * This source code is licensed under the ISC license.
719
734
  * See the LICENSE file in the root directory of this source tree.
720
735
  */
736
+ var Star = createLucideIcon("star", [["path", {
737
+ d: "M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z",
738
+ key: "r04s7s"
739
+ }]]);
740
+ /**
741
+ * @license lucide-react v1.24.0 - ISC
742
+ *
743
+ * This source code is licensed under the ISC license.
744
+ * See the LICENSE file in the root directory of this source tree.
745
+ */
721
746
  var Upload = createLucideIcon("upload", [
722
747
  ["path", {
723
748
  d: "M12 3v12",
@@ -29384,6 +29409,56 @@ var Bubble = memo(function Bubble({ role, text, reasoning, attachments }) {
29384
29409
  });
29385
29410
  //#endregion
29386
29411
  //#region src/ui/AgentPicker.tsx
29412
+ /** Rating pair: "3 star, 3 clock". Two digits read at a glance and stay the same
29413
+ * width on every row, which a free-text hint ("fastest and cheapest" next to
29414
+ * "quick") does not. Both are read the same way — the star is what you get, the
29415
+ * clock is what it costs you in waiting — so a heavier model raises both numbers.
29416
+ * Falls back to the hint when the agent publishes no ratings. */
29417
+ function PresetRating({ quality, time, hint }) {
29418
+ const { t } = useTranslation();
29419
+ if (quality === void 0 || time === void 0) return hint ? /* @__PURE__ */ jsx("span", {
29420
+ className: "shrink-0 text-muted",
29421
+ children: hint
29422
+ }) : null;
29423
+ const both = quality !== time;
29424
+ return /* @__PURE__ */ jsxs("span", {
29425
+ className: "flex shrink-0 items-center gap-1.5 text-muted tabular-nums",
29426
+ children: [/* @__PURE__ */ jsxs("span", {
29427
+ className: "flex items-center gap-0.5",
29428
+ title: t("webchat.quality", { value: quality }),
29429
+ children: [
29430
+ quality,
29431
+ /* @__PURE__ */ jsx(Star, {
29432
+ size: 11,
29433
+ "aria-hidden": true,
29434
+ className: "shrink-0"
29435
+ }),
29436
+ /* @__PURE__ */ jsx("span", {
29437
+ className: "sr-only",
29438
+ children: t("webchat.quality", { value: quality })
29439
+ })
29440
+ ]
29441
+ }), both ? /* @__PURE__ */ jsxs("span", {
29442
+ className: "flex items-center gap-0.5",
29443
+ title: t("webchat.time", { value: time }),
29444
+ children: [
29445
+ time,
29446
+ /* @__PURE__ */ jsx(Clock, {
29447
+ size: 11,
29448
+ "aria-hidden": true,
29449
+ className: "shrink-0"
29450
+ }),
29451
+ /* @__PURE__ */ jsx("span", {
29452
+ className: "sr-only",
29453
+ children: t("webchat.time", { value: time })
29454
+ })
29455
+ ]
29456
+ }) : /* @__PURE__ */ jsx("span", {
29457
+ className: "sr-only",
29458
+ children: t("webchat.time", { value: time })
29459
+ })]
29460
+ });
29461
+ }
29387
29462
  /** Flatten the catalog to one list: with a single registered agent the agent
29388
29463
  * level is noise, and with several the agent name simply prefixes its presets. */
29389
29464
  function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
@@ -29415,6 +29490,8 @@ function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
29415
29490
  presetId: p.id,
29416
29491
  label: multi ? `${a.title} · ${p.title}` : p.title,
29417
29492
  hint: p.hint,
29493
+ quality: p.quality,
29494
+ time: p.time,
29418
29495
  isDefault: p.default === true
29419
29496
  })));
29420
29497
  if (rows.length === 0) return null;
@@ -29438,7 +29515,7 @@ function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
29438
29515
  })]
29439
29516
  }), open && /* @__PURE__ */ jsx("ul", {
29440
29517
  "aria-label": t("webchat.agentPicker"),
29441
- className: "absolute bottom-full z-10 mb-1 max-h-64 w-64 overflow-y-auto rounded-md border border-border bg-elevated py-1 shadow-lg",
29518
+ className: "absolute bottom-full left-0 z-10 mb-1 max-h-64 w-64 max-w-[calc(100vw-2rem)] overflow-y-auto rounded-md border border-border bg-elevated py-1 shadow-lg",
29442
29519
  children: rows.map((r) => {
29443
29520
  const selected = r === current;
29444
29521
  return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", {
@@ -29461,9 +29538,10 @@ function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
29461
29538
  className: "min-w-0 flex-1 truncate",
29462
29539
  children: r.label
29463
29540
  }),
29464
- r.hint && /* @__PURE__ */ jsx("span", {
29465
- className: "shrink-0 text-muted",
29466
- children: r.hint
29541
+ /* @__PURE__ */ jsx(PresetRating, {
29542
+ quality: r.quality,
29543
+ time: r.time,
29544
+ hint: r.hint
29467
29545
  })
29468
29546
  ]
29469
29547
  }) }, `${r.agentId}/${r.presetId}`);
@@ -29550,53 +29628,56 @@ function ChatComposer({ streaming, onSend, onStop, attachments, uploading, uploa
29550
29628
  })
29551
29629
  ]
29552
29630
  }), /* @__PURE__ */ jsxs("div", {
29553
- className: "flex items-end gap-2",
29554
- children: [
29555
- attachmentsAllowed && /* @__PURE__ */ jsxs("label", {
29556
- "aria-label": t("webchat.attach"),
29557
- className: `cursor-pointer rounded-lg p-2 text-muted hover:bg-elevated hover:text-accent ${uploading ? "pointer-events-none opacity-60" : ""}`,
29558
- children: [/* @__PURE__ */ jsx(Paperclip, { size: 18 }), /* @__PURE__ */ jsx("input", {
29559
- type: "file",
29560
- multiple: true,
29561
- className: "hidden",
29562
- disabled: uploading,
29563
- onChange: (e) => {
29564
- if (e.target.files?.length) onPickFiles(e.target.files);
29565
- e.currentTarget.value = "";
29566
- }
29567
- })]
29568
- }),
29569
- /* @__PURE__ */ jsx("textarea", {
29570
- className: "flex-1 resize-none rounded-lg border border-border bg-bg px-3 py-2 text-sm max-h-32",
29571
- rows: 1,
29572
- value: text,
29573
- placeholder: t("webchat.placeholder"),
29574
- onChange: (e) => setText(e.target.value),
29575
- onKeyDown: (e) => {
29576
- if (e.key === "Enter" && !e.shiftKey) {
29577
- e.preventDefault();
29578
- submit();
29579
- }
29631
+ className: "flex flex-col gap-1",
29632
+ children: [/* @__PURE__ */ jsx("textarea", {
29633
+ className: "w-full resize-none rounded-lg border border-border bg-bg px-3 py-2 text-sm max-h-32",
29634
+ rows: 1,
29635
+ value: text,
29636
+ placeholder: t("webchat.placeholder"),
29637
+ onChange: (e) => setText(e.target.value),
29638
+ onKeyDown: (e) => {
29639
+ if (e.key === "Enter" && !e.shiftKey) {
29640
+ e.preventDefault();
29641
+ submit();
29580
29642
  }
29581
- }),
29582
- /* @__PURE__ */ jsx(AgentPicker, {
29583
- agents,
29584
- defaultAgentId,
29585
- selection,
29586
- onChange: onChooseAgent
29587
- }),
29588
- streaming ? /* @__PURE__ */ jsx("button", {
29589
- "aria-label": t("webchat.stop"),
29590
- className: "rounded-lg p-2 hover:bg-elevated",
29591
- onClick: onStop,
29592
- children: /* @__PURE__ */ jsx(Square, { size: 18 })
29593
- }) : /* @__PURE__ */ jsx("button", {
29594
- "aria-label": t("webchat.send"),
29595
- className: "rounded-lg p-2 hover:bg-elevated",
29596
- onClick: submit,
29597
- children: /* @__PURE__ */ jsx(Send, { size: 18 })
29598
- })
29599
- ]
29643
+ }
29644
+ }), /* @__PURE__ */ jsxs("div", {
29645
+ className: "flex items-center gap-1",
29646
+ children: [
29647
+ attachmentsAllowed && /* @__PURE__ */ jsxs("label", {
29648
+ "aria-label": t("webchat.attach"),
29649
+ className: `shrink-0 cursor-pointer rounded-lg p-1.5 text-muted hover:bg-elevated hover:text-accent ${uploading ? "pointer-events-none opacity-60" : ""}`,
29650
+ children: [/* @__PURE__ */ jsx(Paperclip, { size: 18 }), /* @__PURE__ */ jsx("input", {
29651
+ type: "file",
29652
+ multiple: true,
29653
+ className: "hidden",
29654
+ disabled: uploading,
29655
+ onChange: (e) => {
29656
+ if (e.target.files?.length) onPickFiles(e.target.files);
29657
+ e.currentTarget.value = "";
29658
+ }
29659
+ })]
29660
+ }),
29661
+ /* @__PURE__ */ jsx(AgentPicker, {
29662
+ agents,
29663
+ defaultAgentId,
29664
+ selection,
29665
+ onChange: onChooseAgent
29666
+ }),
29667
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
29668
+ streaming ? /* @__PURE__ */ jsx("button", {
29669
+ "aria-label": t("webchat.stop"),
29670
+ className: "shrink-0 rounded-lg p-1.5 hover:bg-elevated",
29671
+ onClick: onStop,
29672
+ children: /* @__PURE__ */ jsx(Square, { size: 18 })
29673
+ }) : /* @__PURE__ */ jsx("button", {
29674
+ "aria-label": t("webchat.send"),
29675
+ className: "shrink-0 rounded-lg p-1.5 hover:bg-elevated",
29676
+ onClick: submit,
29677
+ children: /* @__PURE__ */ jsx(Send, { size: 18 })
29678
+ })
29679
+ ]
29680
+ })]
29600
29681
  })]
29601
29682
  });
29602
29683
  }
package/dist/schema.js CHANGED
@@ -4946,7 +4946,8 @@ function source(raw) {
4946
4946
  hints: {
4947
4947
  ext,
4948
4948
  maxBytes,
4949
- noEditControl: true
4949
+ noEditControl: true,
4950
+ ...o.key ? { fileName: o.key } : {}
4950
4951
  },
4951
4952
  zod: optionalize(s, required)
4952
4953
  });
@@ -6074,6 +6075,15 @@ function wrapKey(opts, meta) {
6074
6075
  ...m,
6075
6076
  hidden: opts.hidden
6076
6077
  };
6078
+ if (opts.derive) m = {
6079
+ ...m,
6080
+ derive: opts.derive,
6081
+ derived: true,
6082
+ hints: {
6083
+ ...m.hints,
6084
+ noEditControl: true
6085
+ }
6086
+ };
6077
6087
  if (opts.key) return {
6078
6088
  key: opts.key,
6079
6089
  type: m
@@ -6412,10 +6422,27 @@ function select(raw) {
6412
6422
  zod: optionalize(s, required)
6413
6423
  }, o.multiple ?? false));
6414
6424
  }
6425
+ /**
6426
+ * Reject anything beyond a flat equality map: an operator key (`$in`, `$and`, …), an
6427
+ * object-valued entry (which is how a nested operator like `{ type: { $in: [...] } }`
6428
+ * would smuggle itself in), or an explicit `undefined` value all throw, naming the
6429
+ * offending key. `undefined` is rejected alongside objects because it would otherwise
6430
+ * slip through every later layer unnoticed: `Condition` types it as a valid value,
6431
+ * `conditionKeys` skips it so compose validates nothing against the target shelf, and
6432
+ * the picker's query-string builder turns it into the literal string `'undefined'`
6433
+ * (an empty picker with no error anywhere).
6434
+ */
6435
+ function assertFlatEqualityFilter(filter) {
6436
+ for (const [key, value] of Object.entries(filter)) {
6437
+ if (key.startsWith("$")) throw new Error(`[relation] filter key '${key}' is an operator — only flat equality is allowed here`);
6438
+ if (value === void 0 || value !== null && typeof value === "object") throw new Error(`[relation] filter key '${key}' has a non-scalar value — only flat equality is allowed here`);
6439
+ }
6440
+ }
6415
6441
  function relation(raw) {
6416
6442
  const o = normalizeOpts(raw);
6417
6443
  const required = o.required ?? false;
6418
6444
  const multi = o.multiple ?? false;
6445
+ if (raw.options.filter) assertFlatEqualityFilter(raw.options.filter);
6419
6446
  let s;
6420
6447
  if (multi) s = unknown().superRefine((raw, ctx) => {
6421
6448
  const arr = jsonValue(raw);
@@ -6437,7 +6464,8 @@ function relation(raw) {
6437
6464
  },
6438
6465
  relation: {
6439
6466
  library: raw.options.library,
6440
- shelf: raw.options.shelf
6467
+ shelf: raw.options.shelf,
6468
+ ...raw.options.filter && { filter: raw.options.filter }
6441
6469
  },
6442
6470
  ...multi && { json: true },
6443
6471
  zod: optionalize(s, required)
@@ -6596,6 +6624,47 @@ function measured(raw) {
6596
6624
  zod: optionalize(s, required)
6597
6625
  });
6598
6626
  }
6627
+ function unit(raw) {
6628
+ const o = normalizeOpts(raw);
6629
+ const required = o.required ?? false;
6630
+ const units = resolveUnits(o.options);
6631
+ const s = string$1(reqErr()).refine((v) => units.some((u) => u.value === v), { message: vmsg("measured_unit") });
6632
+ return wrapKey(o, {
6633
+ kind: "unit",
6634
+ label: o.label ?? "",
6635
+ required,
6636
+ prim: "select",
6637
+ column: "text",
6638
+ hints: { source: typeof o.options === "string" ? o.options : null },
6639
+ options: units.map((u) => ({
6640
+ value: u.value,
6641
+ title: u.label
6642
+ })),
6643
+ zod: optionalize(s, required)
6644
+ });
6645
+ }
6646
+ function amount(raw) {
6647
+ const o = normalizeOpts(raw);
6648
+ const required = o.required ?? false;
6649
+ const cfg = o.config ?? {};
6650
+ let s = number(typeErr());
6651
+ if (cfg.min != null) s = s.min(cfg.min, { message: vmsg("min", { min: cfg.min }) });
6652
+ if (cfg.max != null) s = s.max(cfg.max, { message: vmsg("max", { max: cfg.max }) });
6653
+ return wrapKey(o, {
6654
+ kind: "amount",
6655
+ label: o.label ?? "",
6656
+ required,
6657
+ prim: "number",
6658
+ column: "real",
6659
+ hints: {
6660
+ unitFrom: o.unitFrom,
6661
+ min: cfg.min,
6662
+ max: cfg.max,
6663
+ step: cfg.step ?? "any"
6664
+ },
6665
+ zod: optionalize(s, required)
6666
+ });
6667
+ }
6599
6668
  function money(raw) {
6600
6669
  const o = normalizeOpts(raw);
6601
6670
  const required = o.required ?? false;
@@ -7073,6 +7142,8 @@ var PRIMITIVES = {
7073
7142
  json,
7074
7143
  check,
7075
7144
  measured,
7145
+ unit,
7146
+ amount,
7076
7147
  money,
7077
7148
  code,
7078
7149
  geo,
@@ -7118,7 +7189,7 @@ var field = new Proxy({}, {
7118
7189
  has: (_t, k) => k in composedField()
7119
7190
  });
7120
7191
  function toClient(field) {
7121
- const { kind, label, required, prim, hints, options, relation, virtual, json, hidden } = field;
7192
+ const { kind, label, required, prim, hints, options, relation, virtual, json, hidden, derived } = field;
7122
7193
  return {
7123
7194
  kind,
7124
7195
  label,
@@ -7129,7 +7200,8 @@ function toClient(field) {
7129
7200
  relation,
7130
7201
  virtual,
7131
7202
  json,
7132
- hidden
7203
+ hidden,
7204
+ derived
7133
7205
  };
7134
7206
  }
7135
7207
  //#endregion
package/dist/web.js CHANGED
@@ -11,7 +11,7 @@ import { definePluginUI } from "@coffer-org/web-sdk";
11
11
  var ui_default = definePluginUI({ slots: [{
12
12
  slot: "overlay",
13
13
  id: "webchat",
14
- load: () => import("./ChatWidget-BvI5pRWb.js")
14
+ load: () => import("./ChatWidget-Wxy33-Ei.js")
15
15
  }] });
16
16
  //#endregion
17
17
  export { ui_default as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffer-org/plugin-webchat",
3
- "version": "2.3.0",
3
+ "version": "3.1.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"
@@ -26,9 +26,9 @@
26
26
  "test:ui": "vitest run --root ."
27
27
  },
28
28
  "dependencies": {
29
- "@coffer-org/plugin-orchestrator": "^2.4.0",
30
- "@coffer-org/sdk": "^2.2.1",
31
- "@coffer-org/server": "^2.7.0",
29
+ "@coffer-org/plugin-orchestrator": "^3.1.0",
30
+ "@coffer-org/sdk": "^3.0.0",
31
+ "@coffer-org/server": "^3.0.0",
32
32
  "react-markdown": "^10.1.0",
33
33
  "remark-gfm": "^4.0.1"
34
34
  },