@signalcore/ui 1.0.0-beta.2 → 1.0.0-beta.3

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.
Files changed (55) hide show
  1. package/README.md +47 -21
  2. package/dist/AgentAbout.js +6 -2
  3. package/dist/AgentAbout.js.map +1 -1
  4. package/dist/AgentChat.d.ts +7 -1
  5. package/dist/AgentChat.js +154 -2
  6. package/dist/AgentChat.js.map +1 -1
  7. package/dist/AgentSpace.d.ts +12 -3
  8. package/dist/AgentSpace.js +53 -11
  9. package/dist/AgentSpace.js.map +1 -1
  10. package/dist/Threads.d.ts +73 -0
  11. package/dist/Threads.js +140 -0
  12. package/dist/Threads.js.map +1 -0
  13. package/dist/automation/marks.js +7 -3
  14. package/dist/automation/marks.js.map +1 -1
  15. package/dist/automation/messages.d.ts +4 -0
  16. package/dist/automation/messages.js +5 -1
  17. package/dist/automation/messages.js.map +1 -1
  18. package/dist/automation/when.js +8 -0
  19. package/dist/automation/when.js.map +1 -1
  20. package/dist/client.d.ts +14 -4
  21. package/dist/client.js +7 -2
  22. package/dist/client.js.map +1 -1
  23. package/dist/contract.d.ts +9 -4
  24. package/dist/es.d.ts +2 -0
  25. package/dist/es.js +22 -3
  26. package/dist/es.js.map +1 -1
  27. package/dist/frames.d.ts +7 -1
  28. package/dist/frames.js.map +1 -1
  29. package/dist/index.d.ts +5 -4
  30. package/dist/index.js +5 -4
  31. package/dist/surface/Form.js +1 -1
  32. package/dist/types.d.ts +11 -0
  33. package/dist/types.js.map +1 -1
  34. package/dist/useConversation.d.ts +2 -0
  35. package/dist/useConversation.js +16 -0
  36. package/dist/useConversation.js.map +1 -1
  37. package/dist/widget.js +17 -16
  38. package/package.json +1 -1
  39. package/src/AgentAbout.tsx +4 -0
  40. package/src/AgentChat.tsx +203 -1
  41. package/src/AgentSpace.tsx +89 -13
  42. package/src/Threads.tsx +218 -0
  43. package/src/automation/marks.tsx +7 -0
  44. package/src/automation/messages.ts +8 -0
  45. package/src/automation/when.ts +4 -0
  46. package/src/client.ts +26 -10
  47. package/src/contract.ts +240 -26
  48. package/src/es.ts +21 -0
  49. package/src/frames.ts +3 -1
  50. package/src/index.ts +9 -1
  51. package/src/styles.css +191 -0
  52. package/src/types.ts +11 -0
  53. package/src/useConversation.ts +10 -0
  54. package/src/widget/element.tsx +13 -4
  55. package/src/widget/widget.css +13 -0
package/dist/AgentChat.js CHANGED
@@ -2,6 +2,7 @@ import { actionWords } from "./about.js";
2
2
  import { classes } from "./classes.js";
3
3
  import { KIT_MESSAGES } from "./kit/messages.js";
4
4
  import { KitProvider } from "./kit/context.js";
5
+ import { Button } from "./kit/Button.js";
5
6
  import { actionMark } from "./marks.js";
6
7
  import { BROWSER_MESSAGES, BrowserView } from "./BrowserView.js";
7
8
  import { channelParts } from "./channelParts.js";
@@ -10,6 +11,8 @@ import { useReveal } from "./reveal.js";
10
11
  import { useConversationFiles } from "./conversationFiles.js";
11
12
  import "./frames.js";
12
13
  import { AudioPlayer } from "./kit/AudioPlayer.js";
14
+ import { Field } from "./kit/Field.js";
15
+ import { Checkbox, NumberInput, TextArea, TextInput } from "./kit/Inputs.js";
13
16
  import { FileChips, UploadChips, carriesFiles, useUploads } from "./kit/FileField.js";
14
17
  import { stepGlyph } from "./stepGlyphs.js";
15
18
  import { Markdown } from "./surface/Markdown.js";
@@ -54,6 +57,10 @@ const CHAT_MESSAGES = {
54
57
  inputs: {},
55
58
  allow: "Allow",
56
59
  decline: "Decline",
60
+ approvals: (count) => `${count} actions need your approval`,
61
+ approveAll: "Approve all",
62
+ approveSome: (count) => `Approve ${count}`,
63
+ declineAll: "Decline all",
57
64
  confirming: "Waiting for the business to confirm",
58
65
  handedOff: "Handed to a person",
59
66
  handover: (holder, by, name) => {
@@ -125,6 +132,7 @@ function Chat({ client, conversationId, name, messages, classNames, components,
125
132
  onTool: onTool ?? (() => Promise.reject(new Error(words.noTool)))
126
133
  });
127
134
  const files = useConversationFiles(client, conversationId, live.messages, onOpenFile);
135
+ useReadAsShown(client, conversationId, live.messages.at(-1)?.id);
128
136
  const uploads = useUploads(attachments ? files : void 0, { max: 10 });
129
137
  const attaching = attachments && (write !== void 0 || live.awaiting === null);
130
138
  const [over, setOver] = useState(false);
@@ -449,6 +457,7 @@ function Log({ live, name, words, classNames, components, locale, following, too
449
457
  components,
450
458
  locale,
451
459
  onAction: live.act,
460
+ onActAll: live.actAll,
452
461
  builder,
453
462
  files,
454
463
  who,
@@ -670,8 +679,9 @@ function DeliveryMark({ message, words, classNames }) {
670
679
  * for (on a person's page, one the business allows is a quiet line instead); on a channel, where
671
680
  * it got to.
672
681
  */
673
- function Reply({ message, from, awaiting, current, answers, name, words, classNames, components, locale, onAction, builder, files, who, steps, onChannel }) {
682
+ function Reply({ message, from, awaiting, current, answers, name, words, classNames, components, locale, onAction, builder, files, who, steps, onChannel, onActAll }) {
674
683
  const text = useReveal(message.text, from ?? message.text.length);
684
+ const asked = message.steps.filter((step) => step.request !== null && !(step.request.decides === "business" && who === "person"));
675
685
  const { shown: said, typing } = onChannel ? channelParts(message, text) : {
676
686
  shown: [text],
677
687
  typing: false
@@ -735,9 +745,16 @@ function Reply({ message, from, awaiting, current, answers, name, words, classNa
735
745
  })
736
746
  })
737
747
  }, step.id)),
748
+ asked.length > 1 && /* @__PURE__ */ jsx(Approvals, {
749
+ steps: asked,
750
+ name,
751
+ words,
752
+ classNames,
753
+ onActAll
754
+ }, asked.map((step) => step.id).join()),
738
755
  message.steps.map((step) => {
739
756
  const { request } = step;
740
- if (request === null) return null;
757
+ if (request === null || asked.length > 1 && asked.some((one) => one.id === step.id)) return null;
741
758
  if (request.decides === "business" && who === "person") return /* @__PURE__ */ jsxs("p", {
742
759
  part: "confirming",
743
760
  className: classes(classNames, "sc-chat__confirming"),
@@ -938,6 +955,141 @@ function Approval({ request, name, words, classNames, components, locale, onActi
938
955
  });
939
956
  }
940
957
  /**
958
+ * Several actions a reply waits on the person's leave for, in one card: a box to tick for each
959
+ * (all ticked to begin with), what each would take under it, editable while ticked, and one
960
+ * button that sends a yes for each ticked (with what was changed) and a no for the rest, at once.
961
+ */
962
+ function Approvals({ steps, name, words, classNames, onActAll }) {
963
+ const [chosen, setChosen] = useState(() => new Set(steps.map((step) => step.id)));
964
+ const [edits, setEdits] = useState({});
965
+ const [sent, setSent] = useState(false);
966
+ const inputOf = (step) => ({
967
+ ...step.request.input,
968
+ ...edits[step.id]
969
+ });
970
+ const send = () => {
971
+ setSent(true);
972
+ onActAll(steps.map((step) => chosen.has(step.id) ? {
973
+ surface: step.id,
974
+ name: "approve",
975
+ data: step.id in edits ? { input: inputOf(step) } : {},
976
+ label: words.allow
977
+ } : {
978
+ surface: step.id,
979
+ name: "decline",
980
+ data: {},
981
+ label: words.decline
982
+ }));
983
+ };
984
+ const label = chosen.size === steps.length ? words.approveAll : chosen.size === 0 ? words.declineAll : words.approveSome(chosen.size);
985
+ return /* @__PURE__ */ jsxs("div", {
986
+ part: "approval",
987
+ className: classes(classNames, "sc-chat__request", "sc-chat__requests"),
988
+ children: [
989
+ /* @__PURE__ */ jsxs("header", { children: [/* @__PURE__ */ jsx(ShieldWarningIcon, { "aria-hidden": true }), words.approvals(steps.length)] }),
990
+ /* @__PURE__ */ jsx("ul", { children: steps.map((step) => {
991
+ const ticked = chosen.has(step.id);
992
+ return /* @__PURE__ */ jsxs("li", {
993
+ className: classes(classNames, "sc-chat__ask"),
994
+ children: [/* @__PURE__ */ jsx(Checkbox, {
995
+ value: ticked,
996
+ disabled: sent,
997
+ onChange: (on) => {
998
+ setChosen((last) => {
999
+ const next = new Set(last);
1000
+ if (on) next.add(step.id);
1001
+ else next.delete(step.id);
1002
+ return next;
1003
+ });
1004
+ },
1005
+ children: words.request(name, step.request.action)
1006
+ }), ticked && /* @__PURE__ */ jsx("div", {
1007
+ className: classes(classNames, "sc-chat__ask-fields"),
1008
+ children: Object.entries(inputOf(step)).map(([field, value]) => /* @__PURE__ */ jsx(InputValue, {
1009
+ label: words.inputs[field] ?? field,
1010
+ value,
1011
+ disabled: sent,
1012
+ onChange: (changed) => {
1013
+ setEdits((last) => ({
1014
+ ...last,
1015
+ [step.id]: {
1016
+ ...last[step.id],
1017
+ [field]: changed
1018
+ }
1019
+ }));
1020
+ }
1021
+ }, field))
1022
+ })]
1023
+ }, step.id);
1024
+ }) }),
1025
+ /* @__PURE__ */ jsx("footer", { children: /* @__PURE__ */ jsx(Button, {
1026
+ tone: "primary",
1027
+ disabled: sent,
1028
+ onClick: send,
1029
+ children: label
1030
+ }) })
1031
+ ]
1032
+ });
1033
+ }
1034
+ /**
1035
+ * One field of an action's input, editable as what it holds: words, a number, a yes or no;
1036
+ * anything else (a list, an object) shown as it is.
1037
+ */
1038
+ function InputValue({ label, value, disabled, onChange }) {
1039
+ if (typeof value === "boolean") return /* @__PURE__ */ jsx(Checkbox, {
1040
+ value,
1041
+ disabled,
1042
+ onChange,
1043
+ children: label
1044
+ });
1045
+ return /* @__PURE__ */ jsx(Field, {
1046
+ label,
1047
+ children: (ids) => typeof value === "number" ? /* @__PURE__ */ jsx(NumberInput, {
1048
+ ...ids,
1049
+ value,
1050
+ disabled,
1051
+ onChange
1052
+ }) : typeof value === "string" ? value.length > 80 || value.includes("\n") ? /* @__PURE__ */ jsx(TextArea, {
1053
+ ...ids,
1054
+ value,
1055
+ disabled,
1056
+ onChange
1057
+ }) : /* @__PURE__ */ jsx(TextInput, {
1058
+ ...ids,
1059
+ value,
1060
+ disabled,
1061
+ onChange
1062
+ }) : /* @__PURE__ */ jsx(TextInput, {
1063
+ ...ids,
1064
+ value: JSON.stringify(value),
1065
+ disabled: true,
1066
+ onChange: () => void 0
1067
+ })
1068
+ });
1069
+ }
1070
+ /**
1071
+ * On a person's page, what came is read as the chat shows it: each newest message while the page
1072
+ * is in sight, and once it comes back into sight. Their threads' `unread` follows.
1073
+ */
1074
+ function useReadAsShown(client, conversationId, newest) {
1075
+ useEffect(() => {
1076
+ if (client.who !== "person" || newest === void 0) return void 0;
1077
+ const read = () => {
1078
+ if (document.visibilityState === "hidden") return;
1079
+ client.conversations.read(conversationId).catch(() => void 0);
1080
+ };
1081
+ read();
1082
+ document.addEventListener("visibilitychange", read);
1083
+ return () => {
1084
+ document.removeEventListener("visibilitychange", read);
1085
+ };
1086
+ }, [
1087
+ client,
1088
+ conversationId,
1089
+ newest
1090
+ ]);
1091
+ }
1092
+ /**
941
1093
  * Where the person writes: a box that grows a few lines with the text. Enter sends, Shift+Enter
942
1094
  * breaks the line; nothing goes while a reply is on its way or the socket is closed, but the
943
1095
  * person can keep writing. While the turn waits for them to act above, the box is closed and