@signalcore/ui 1.0.0-beta.3 → 1.0.0-beta.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.
Files changed (71) hide show
  1. package/README.md +79 -0
  2. package/dist/AgentChat.d.ts +19 -3
  3. package/dist/AgentChat.js +149 -27
  4. package/dist/AgentChat.js.map +1 -1
  5. package/dist/AgentSpace.d.ts +14 -2
  6. package/dist/AgentSpace.js +3 -1
  7. package/dist/AgentSpace.js.map +1 -1
  8. package/dist/View.d.ts +48 -0
  9. package/dist/View.js +101 -0
  10. package/dist/View.js.map +1 -0
  11. package/dist/blocks.d.ts +83 -2
  12. package/dist/client.d.ts +9 -1
  13. package/dist/client.js +1 -0
  14. package/dist/client.js.map +1 -1
  15. package/dist/es.js +12 -0
  16. package/dist/es.js.map +1 -1
  17. package/dist/index.d.ts +11 -5
  18. package/dist/index.js +7 -1
  19. package/dist/kit/Agenda.d.ts +25 -0
  20. package/dist/kit/Agenda.js +119 -0
  21. package/dist/kit/Agenda.js.map +1 -0
  22. package/dist/kit/Combobox.js +1 -1
  23. package/dist/kit/DateField.js +1 -1
  24. package/dist/kit/Kanban.d.ts +17 -0
  25. package/dist/kit/Kanban.js +157 -0
  26. package/dist/kit/Kanban.js.map +1 -0
  27. package/dist/kit/TimeField.js +1 -1
  28. package/dist/kit/Timeline.d.ts +14 -0
  29. package/dist/kit/Timeline.js +71 -0
  30. package/dist/kit/Timeline.js.map +1 -0
  31. package/dist/kit/context.d.ts +1 -1
  32. package/dist/kit/context.js.map +1 -1
  33. package/dist/kit/messages.d.ts +22 -0
  34. package/dist/kit/messages.js +10 -0
  35. package/dist/kit/messages.js.map +1 -1
  36. package/dist/surface/Placed.d.ts +20 -0
  37. package/dist/surface/Placed.js +70 -0
  38. package/dist/surface/Placed.js.map +1 -0
  39. package/dist/surface/Surface.d.ts +4 -2
  40. package/dist/surface/Surface.js +78 -2
  41. package/dist/surface/Surface.js.map +1 -1
  42. package/dist/surface/places.d.ts +16 -0
  43. package/dist/surface/places.js +15 -0
  44. package/dist/surface/places.js.map +1 -0
  45. package/dist/surface/scope.d.ts +17 -1
  46. package/dist/surface/scope.js +1 -0
  47. package/dist/surface/scope.js.map +1 -1
  48. package/dist/types.d.ts +12 -0
  49. package/dist/types.js.map +1 -1
  50. package/dist/widget.js +17 -17
  51. package/package.json +1 -1
  52. package/src/AgentChat.tsx +215 -34
  53. package/src/AgentSpace.tsx +17 -1
  54. package/src/View.tsx +117 -0
  55. package/src/blocks.ts +100 -2
  56. package/src/client.ts +16 -0
  57. package/src/contract.ts +166 -5
  58. package/src/es.ts +9 -0
  59. package/src/index.ts +27 -2
  60. package/src/kit/Agenda.tsx +161 -0
  61. package/src/kit/Kanban.tsx +182 -0
  62. package/src/kit/Timeline.tsx +58 -0
  63. package/src/kit/context.tsx +45 -1
  64. package/src/kit/messages.ts +31 -0
  65. package/src/styles.css +568 -0
  66. package/src/surface/Placed.tsx +98 -0
  67. package/src/surface/Surface.tsx +92 -1
  68. package/src/surface/places.ts +19 -0
  69. package/src/surface/scope.ts +24 -1
  70. package/src/types.ts +13 -0
  71. package/src/widget/element.tsx +41 -3
package/README.md CHANGED
@@ -185,6 +185,39 @@ couldn't begin again), `close`, `agent` (its name when the face can't be read),
185
185
  (`--text`, `--voice`; `aria-checked` on the one on), `sc-space__caption` (`--person`, `--agent`),
186
186
  `sc-space__status`.
187
187
 
188
+ ### Panels, modals and your own blocks
189
+
190
+ What the agent shows can stand apart from the conversation (`show` and `ask_person` with
191
+ `where`, v3/UI.md, Surfaces): in a **panel** beside the chat (over it, where the space is narrow:
192
+ the rep's week while they talk), or in a **modal** over the page (something to answer now; once
193
+ used it closes and the chat goes on). Each opens as it comes, and a conversation that opens
194
+ waiting on one opens it; the reply keeps a quiet line, "Ray abrió tu semana" (`opened`), that
195
+ opens it again. Your page decides what it allows: `surfaces={{ panel: true, modal: false }}`
196
+ (both by default); a place left out shows in the conversation, as on a channel. `AgentChat`
197
+ takes the same `surfaces`.
198
+
199
+ `blocks` registers your own blocks by the name each was declared with (`PUT /v1/blocks/{name}`):
200
+ `blocks={{ "demoprep.deal-card": DealCard }}`. The agent gives only props, which the API checked
201
+ against your schema; your component gets `{props, text, active, onAction}` (`HostBlockProps`),
202
+ and a block it has no component for shows its words in a quiet card (`sc-surface__host`).
203
+
204
+ ```tsx
205
+ function DealCard({ props }: HostBlockProps<{ title: string; amount: number }>) {
206
+ return <YourDealCard title={props.title} amount={props.amount} />;
207
+ }
208
+
209
+ <AgentSpace
210
+ client={client}
211
+ agentId="agt_…"
212
+ surfaces={{ panel: true, modal: true }}
213
+ blocks={{ "demoprep.deal-card": DealCard }}
214
+ />;
215
+ ```
216
+
217
+ Words: `opened(name, title)`, `closePlaced`. Parts: `sc-chat__placed` (`--open`), `sc-chat--placed`
218
+ (a panel is open), `sc-placed` (`--panel`, `--modal`, the latter a `<dialog>`),
219
+ `sc-placed__head`, `sc-placed__title`, `sc-placed__body`.
220
+
188
221
  ### Threads, unread, and several yeses at once
189
222
 
190
223
  A person keeps several conversations (threads) with the agent, each with its title: one your
@@ -257,6 +290,10 @@ flow at the element's size (560px tall unless your CSS says). A visitor's conver
257
290
  | `stylesheet` | your own CSS for the widget: the CSS itself (anything with a `{`) or its address; adopted inside the shadow root, after ours. Also the element's `stylesheet` property |
258
291
  | `unstyled` | our look's defaults drop away (grounds, lines, shadows, corners, every ink but the page's text), the layout stays: for a host that styles everything |
259
292
  | `shadow` | `none`: no shadow root; the widget draws in the page (our stylesheet goes first in `<head>`, so your CSS wins), for a host whose global CSS should apply directly. Read once, as the element connects |
293
+ | `surfaces` | where the agent may put what it shows besides the conversation: `panel`, `modal`, both (`"panel modal"`, the default) or none (`""`) |
294
+
295
+ Your own blocks go as the element's `blocks` property, React components the widget's React
296
+ draws: `document.querySelector("signalcore-chat").blocks = { "demoprep.deal-card": DealCard }`.
260
297
 
261
298
  By default the widget takes your page's type and ink: the font, the base size and the text colour
262
299
  the element inherits come through the shadow root and stand in for our defaults (read as it draws,
@@ -731,6 +768,48 @@ works, a check and "Conectada" in its place (`sc-surface__connect--done`), and a
731
768
  it failed. It changes as the connection does, from the `ui` frames the API sends again on the
732
769
  same id; before the API has asked for it (`connection` null), only the app as the agent named it.
733
770
 
771
+ The kit's blocks of time and work: a `calendar` (`Agenda`: a day's events, or a week's from its
772
+ `start`, at their hours; one with a `url` opens its page, with the calendar's `action` the rest
773
+ send `{event}`; narrow, the days go one under another, an empty one a quiet line; 680px and up,
774
+ the week stands as seven columns), a `timeline` (`Timeline`: dated things the newest first, each
775
+ a dot on a hairline, in its `tone` when it has one, its `status` a quiet word), a `board`
776
+ (`Kanban`: cards in columns; with its `action`, a card dragged to another column, or moved from
777
+ its button's list of the others, goes there at once and sends `{card, column}`, labelled
778
+ `board.moved`). A `host` block is one of yours, drawn by the component `blocks` registers under
779
+ its `name` (a component that fails as it draws leaves its words, and the rest draws). Words:
780
+ `calendar` (`allDay`, `empty`), `board` (`move`, `moveTo`, `moved`, `empty`). Parts:
781
+ `sc-agenda` (`--day`), `sc-agenda__days`, `sc-agenda__day` (`--today`, `--empty`),
782
+ `sc-agenda__date`, `sc-agenda__weekday`, `sc-agenda__number`, `sc-agenda__events`,
783
+ `sc-agenda__event`, `sc-agenda__time`, `sc-agenda__what`, `sc-agenda__title`,
784
+ `sc-agenda__detail`, `sc-agenda__empty`; `sc-timeline`, `sc-timeline__item` (`--info`,
785
+ `--success`, `--warning`, `--danger`), `sc-timeline__dot`, `sc-timeline__body`,
786
+ `sc-timeline__head`, `sc-timeline__title`, `sc-timeline__status`, `sc-timeline__when`,
787
+ `sc-timeline__text`; `sc-board`, `sc-board__column` (`--over`), `sc-board__head`,
788
+ `sc-board__title`, `sc-board__count`, `sc-board__cards`, `sc-board__card` (`--dragging`),
789
+ `sc-board__card-title`, `sc-board__card-subtitle`, `sc-board__card-meta`, `sc-board__move`,
790
+ `sc-board__menu`, `sc-board__empty`.
791
+
792
+ ## View
793
+
794
+ `<View client name contact? blocks? components? classNames? messages? locale? onAction? />` draws
795
+ an agent's view where your page puts it: what the agent keeps on show for each person (their
796
+ next meeting, their week), a dashboard's spec rendered for one contact with their own rows
797
+ (`GET /views/{name}/render`). With a person's token it is theirs; for the business, `contact`
798
+ names whom (their id). It is read when shown and again every `VIEW_POLL_MS` (30 s), so it shows
799
+ what the rows hold now; while it is first read an empty box holds its place, and a view that
800
+ isn't there (or isn't the person's) draws nothing. `useView(client, name, contact?)` gives
801
+ `{view, problem, gone}` for your own layout (`view.title`, `view.blocks`, `view.text`).
802
+
803
+ ```tsx
804
+ <View
805
+ client={client}
806
+ name="proxima_reunion"
807
+ blocks={{ "demoprep.deal-card": DealCard }}
808
+ messages={KIT_MESSAGES_ES}
809
+ locale={es}
810
+ />
811
+ ```
812
+
734
813
  ## AgentAbout
735
814
 
736
815
  `<AgentAbout client agentId name messages? classNames? controls? apps? locale? view? onViewChange? onOpenFile? onOpenConversation? />`: Qué puede, made to sit
@@ -5,7 +5,9 @@ import { KitPart } from "./kit/context.js";
5
5
  import { ToolHandler } from "./useConversation.js";
6
6
  import { BrowserMessages, BrowserPart } from "./BrowserView.js";
7
7
  import { Dictate } from "./useDictation.js";
8
- import { SurfaceComponents, SurfacePart } from "./surface/scope.js";
8
+ import { PlacedPart } from "./surface/Placed.js";
9
+ import { SurfacePlaces } from "./surface/places.js";
10
+ import { HostBlocks, SurfaceComponents, SurfacePart } from "./surface/scope.js";
9
11
  import { ReactNode } from "react";
10
12
  //#region src/AgentChat.d.ts
11
13
  /** The words the chat says; a host gives its own, in its language. */
@@ -74,6 +76,13 @@ export interface ChatMessages {
74
76
  confirming: string;
75
77
  /** The line across the log where the agent handed the conversation to a person. */
76
78
  handedOff: string;
79
+ /**
80
+ * The line where the agent put what it showed in a panel or a modal, which opens it again:
81
+ * the agent's name and the surface's title ("Ray opened your week").
82
+ */
83
+ opened: (name: string, title: string) => string;
84
+ /** The button that closes a panel or a modal. */
85
+ closePlaced: string;
77
86
  /**
78
87
  * The line across the log where who answers the conversation changed (`handovers`), before
79
88
  * why: to whom (`holder`), and who changed it (`by`).
@@ -108,8 +117,8 @@ export interface ChatMessages {
108
117
  }
109
118
  export declare const CHAT_MESSAGES: ChatMessages;
110
119
  /** The chat's parts, by the class each carries; `classNames` adds a host's own under the same keys. */
111
- export type ChatPart = "sc-chat" | "sc-chat--builder" | "sc-chat--console" | "sc-chat__log" | "sc-chat__log--channel" | "sc-chat__message" | "sc-chat__message--person" | "sc-chat__message--agent" | "sc-chat__message--action" | "sc-chat__message--business" | "sc-chat__said" | "sc-chat__business" | "sc-chat__meta" | "sc-chat__delivery" | "sc-chat__text" | "sc-chat__steps" | "sc-chat__step" | "sc-chat__mark" | "sc-chat__step--running" | "sc-chat__step--queued" | "sc-chat__step--waiting" | "sc-chat__step--done" | "sc-chat__step--failed" | "sc-chat__output" | "sc-chat__request" | "sc-chat__requests" | "sc-chat__ask" | "sc-chat__ask-fields" | "sc-chat__confirming" | "sc-chat__screen" | "sc-chat__screen--bare" | "sc-chat__handoff" | "sc-chat__handover" | "sc-chat__decision" | "sc-chat__message--heard" | "sc-chat__message--voice" | "sc-chat__transcript" | "sc-chat__cut" | "sc-chat__reply" | "sc-chat__status" | "sc-chat__done" | "sc-chat__empty" | "sc-chat__suggestion" | "sc-chat__composer" | "sc-chat__files" | "sc-chat__attach" | "sc-chat__dictate" | "sc-chat__typing" | "sc-chat__dictation" | "sc-chat__drop" | "sc-chat__input" | "sc-chat__actions" | "sc-chat__send" | "sc-chat__stop";
112
- type ClassNames = Partial<Record<ChatPart | SurfacePart | KitPart | BrowserPart, string>> | undefined;
120
+ export type ChatPart = "sc-chat" | "sc-chat--builder" | "sc-chat--console" | "sc-chat--placed" | "sc-chat__log" | "sc-chat__log--channel" | "sc-chat__message" | "sc-chat__message--person" | "sc-chat__message--agent" | "sc-chat__message--action" | "sc-chat__message--business" | "sc-chat__said" | "sc-chat__business" | "sc-chat__meta" | "sc-chat__delivery" | "sc-chat__text" | "sc-chat__steps" | "sc-chat__step" | "sc-chat__mark" | "sc-chat__step--running" | "sc-chat__step--queued" | "sc-chat__step--waiting" | "sc-chat__step--done" | "sc-chat__step--failed" | "sc-chat__output" | "sc-chat__request" | "sc-chat__requests" | "sc-chat__ask" | "sc-chat__ask-fields" | "sc-chat__confirming" | "sc-chat__screen" | "sc-chat__screen--bare" | "sc-chat__handoff" | "sc-chat__placed" | "sc-chat__placed--open" | "sc-chat__handover" | "sc-chat__decision" | "sc-chat__message--heard" | "sc-chat__message--voice" | "sc-chat__transcript" | "sc-chat__cut" | "sc-chat__reply" | "sc-chat__status" | "sc-chat__done" | "sc-chat__empty" | "sc-chat__suggestion" | "sc-chat__composer" | "sc-chat__files" | "sc-chat__attach" | "sc-chat__dictate" | "sc-chat__typing" | "sc-chat__dictation" | "sc-chat__drop" | "sc-chat__input" | "sc-chat__actions" | "sc-chat__send" | "sc-chat__stop";
121
+ type ClassNames = Partial<Record<ChatPart | SurfacePart | KitPart | BrowserPart | PlacedPart, string>> | undefined;
113
122
  /**
114
123
  * How the chat looks. `agent` is an agent's conversation. `builder` is the builder's, v2's: the
115
124
  * working line says what it does under the brand's sweeping light, the steps open on what they
@@ -152,6 +161,13 @@ export interface AgentChatProps {
152
161
  classNames?: ClassNames;
153
162
  /** The host's own drawing of a block type, for every surface the agent shows. */
154
163
  components?: SurfaceComponents | undefined;
164
+ /** The host's own blocks, by the name each was declared with (`PUT /v1/blocks/{name}`). */
165
+ blocks?: HostBlocks | undefined;
166
+ /**
167
+ * Where the agent may put what it shows besides the conversation: a panel beside it, a modal
168
+ * over the page (both by default). One the host doesn't allow shows in the conversation.
169
+ */
170
+ surfaces?: SurfacePlaces | undefined;
155
171
  /** The language of the surfaces' calendars, days, times and numbers; English by default. */
156
172
  locale?: KitLocale | undefined;
157
173
  /** An error's code, from the socket or `load` for a history that could not be read; the host words it. */
package/dist/AgentChat.js CHANGED
@@ -16,10 +16,12 @@ import { Checkbox, NumberInput, TextArea, TextInput } from "./kit/Inputs.js";
16
16
  import { FileChips, UploadChips, carriesFiles, useUploads } from "./kit/FileField.js";
17
17
  import { stepGlyph } from "./stepGlyphs.js";
18
18
  import { Markdown } from "./surface/Markdown.js";
19
+ import { Placed } from "./surface/Placed.js";
20
+ import { placeOf } from "./surface/places.js";
19
21
  import { Surface, framesItself, usePlayed } from "./surface/Surface.js";
20
22
  import { NO_TOOL, useConversation } from "./useConversation.js";
21
23
  import { Fragment, useEffect, useLayoutEffect, useRef, useState } from "react";
22
- import { ArrowUpIcon, CaretRightIcon, CheckIcon, ChecksIcon, ClockIcon, MicrophoneIcon, PaperclipIcon, ShieldWarningIcon, StopIcon, UploadSimpleIcon, WarningCircleIcon } from "@phosphor-icons/react";
24
+ import { ArrowUpIcon, ArrowsOutSimpleIcon, CaretRightIcon, CheckIcon, ChecksIcon, ClockIcon, MicrophoneIcon, PaperclipIcon, ShieldWarningIcon, SidebarSimpleIcon, StopIcon, UploadSimpleIcon, WarningCircleIcon } from "@phosphor-icons/react";
23
25
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
24
26
  //#region src/AgentChat.tsx
25
27
  const CHAT_MESSAGES = {
@@ -63,6 +65,8 @@ const CHAT_MESSAGES = {
63
65
  declineAll: "Decline all",
64
66
  confirming: "Waiting for the business to confirm",
65
67
  handedOff: "Handed to a person",
68
+ opened: (name, title) => `${name} opened ${title}`,
69
+ closePlaced: "Close",
66
70
  handover: (holder, by, name) => {
67
71
  const automation = by.kind === "automation" ? ` by “${by.title}”` : "";
68
72
  switch (holder) {
@@ -93,6 +97,70 @@ const CHAT_MESSAGES = {
93
97
  kit: KIT_MESSAGES,
94
98
  browser: BROWSER_MESSAGES
95
99
  };
100
+ /** Where nothing is put apart: a conversation on a channel. */
101
+ const NO_PLACES = {};
102
+ /**
103
+ * What the person can still do on the conversation's surfaces: what they did on each, by its id
104
+ * (each draws its answer), and whether one takes input. A surface takes input while the turn
105
+ * waits on it or, once nothing runs or waits, when the agent showed it since the person last
106
+ * spoke; older ones are read, not used. On a channel the person answered there: here it reads.
107
+ */
108
+ function surfacesOf(live, onChannel) {
109
+ const { messages, pending, awaiting } = live;
110
+ const idle = !onChannel && !pending && awaiting === null;
111
+ const lastSaid = messages.findLastIndex((message) => message.role === "user");
112
+ const answers = /* @__PURE__ */ new Map();
113
+ for (const message of messages) if (message.action) answers.set(message.action.surface, message.action);
114
+ const current = (index) => idle && index > lastSaid;
115
+ return {
116
+ answers,
117
+ current,
118
+ takes: (id, index) => !onChannel && id === awaiting || current(index)
119
+ };
120
+ }
121
+ /** A step that drew a surface, by its id: the surface, and the index of its reply. */
122
+ function placedStep(messages, id) {
123
+ for (const [index, message] of messages.entries()) {
124
+ const step = message.steps.find((each) => each.id === id);
125
+ if (step?.surface) return {
126
+ step,
127
+ surface: step.surface,
128
+ index
129
+ };
130
+ }
131
+ return null;
132
+ }
133
+ /**
134
+ * The surface open in a panel or a modal, by its step's id: each one the agent puts apart opens
135
+ * as it comes, and a conversation that opens waiting on one opens it; one read with the history
136
+ * waits for its line. Closing it, or opening another, is the person's.
137
+ */
138
+ function useOpenedPlace(live, places) {
139
+ const [opened, setOpened] = useState(null);
140
+ const ids = live.messages.flatMap((message) => message.steps).filter((step) => step.surface && placeOf(step.surface, places) !== "chat").map((step) => step.id);
141
+ const seen = useRef(null);
142
+ const key = ids.join(" ");
143
+ const { loading, awaiting } = live;
144
+ useEffect(() => {
145
+ if (loading) return;
146
+ const known = seen.current;
147
+ const placed = key === "" ? [] : key.split(" ");
148
+ if (known === null) {
149
+ seen.current = new Set(placed);
150
+ if (awaiting !== null && placed.includes(awaiting)) setOpened(awaiting);
151
+ return;
152
+ }
153
+ const fresh = placed.filter((id) => !known.has(id));
154
+ for (const id of fresh) known.add(id);
155
+ const newest = fresh.at(-1);
156
+ if (newest !== void 0) setOpened(newest);
157
+ }, [
158
+ key,
159
+ loading,
160
+ awaiting
161
+ ]);
162
+ return [opened, setOpened];
163
+ }
96
164
  /** How close to the foot, in pixels, still counts as being at it (v2's). */
97
165
  const NEAR_FOOT = 80;
98
166
  /**
@@ -119,7 +187,7 @@ function AgentChat(props) {
119
187
  }, props.conversationId)
120
188
  });
121
189
  }
122
- function Chat({ client, conversationId, name, messages, classNames, components, locale, onError, actions, suggestions, compose, look = "agent", steps = client.who === "person" ? "hidden" : "shown", attachments = true, onOpenFile, dictate, write, handovers = false, onHolder, onStep, handover, onTool, taken }) {
190
+ function Chat({ client, conversationId, name, messages, classNames, components, blocks, surfaces, locale, onError, actions, suggestions, compose, look = "agent", steps = client.who === "person" ? "hidden" : "shown", attachments = true, onOpenFile, dictate, write, handovers = false, onHolder, onStep, handover, onTool, taken }) {
123
191
  const words = {
124
192
  ...CHAT_MESSAGES,
125
193
  ...messages
@@ -142,6 +210,9 @@ function Chat({ client, conversationId, name, messages, classNames, components,
142
210
  const builder = look !== "agent";
143
211
  const { pending } = live;
144
212
  const onChannel = live.messages.some((message) => message.author != null);
213
+ const places = onChannel ? NO_PLACES : surfaces;
214
+ const [opened, setOpened] = useOpenedPlace(live, places);
215
+ const open = opened === null ? null : placedStep(live.messages, opened);
145
216
  const stop = useRef(live.stop);
146
217
  useEffect(() => {
147
218
  stop.current = live.stop;
@@ -158,6 +229,7 @@ function Chat({ client, conversationId, name, messages, classNames, components,
158
229
  }, [builder, pending]);
159
230
  const looks = builder ? ["sc-chat--builder"] : [];
160
231
  if (look === "console") looks.push("sc-chat--console");
232
+ if (open !== null && placeOf(open.surface, places) === "panel") looks.push("sc-chat--placed");
161
233
  const stepsShown = steps === "shown";
162
234
  return /* @__PURE__ */ jsxs("div", {
163
235
  part: "chat",
@@ -211,6 +283,10 @@ function Chat({ client, conversationId, name, messages, classNames, components,
211
283
  words,
212
284
  classNames,
213
285
  components,
286
+ blocks,
287
+ places,
288
+ opened,
289
+ onOpen: setOpened,
214
290
  locale,
215
291
  following,
216
292
  took,
@@ -221,6 +297,33 @@ function Chat({ client, conversationId, name, messages, classNames, components,
221
297
  who: client.who,
222
298
  steps: stepsShown
223
299
  }),
300
+ open !== null && /* @__PURE__ */ jsx(Placed, {
301
+ where: placeOf(open.surface, places) === "modal" ? "modal" : "panel",
302
+ title: open.surface.title ?? "",
303
+ closeLabel: words.closePlaced,
304
+ onClose: () => {
305
+ setOpened(null);
306
+ },
307
+ classNames,
308
+ children: /* @__PURE__ */ jsx(Surface, {
309
+ surface: open.surface,
310
+ active: surfacesOf(live, onChannel).takes(open.step.id, open.index),
311
+ answered: surfacesOf(live, onChannel).answers.get(open.step.id),
312
+ components,
313
+ blocks,
314
+ classNames,
315
+ messages: words.kit,
316
+ locale,
317
+ files,
318
+ onAction: (action) => {
319
+ live.act({
320
+ surface: open.step.id,
321
+ ...action
322
+ });
323
+ if (placeOf(open.surface, places) === "modal") setOpened(null);
324
+ }
325
+ })
326
+ }, open.step.id),
224
327
  /* @__PURE__ */ jsx(BrowserView, {
225
328
  live,
226
329
  name,
@@ -356,7 +459,7 @@ function whenSaid(at, now, code) {
356
459
  * one quiet line across the log says so, and under a person's message the agent let pass, that
357
460
  * it did. Screen readers hear a reply once, when it is stored.
358
461
  */
359
- function Log({ live, name, words, classNames, components, locale, following, took, builder, onChannel, files, handovers, who, steps }) {
462
+ function Log({ live, name, words, classNames, components, locale, following, took, builder, onChannel, files, handovers, who, steps, blocks, places, opened, onOpen }) {
360
463
  const scroller = useRef(null);
361
464
  const said = useRef(null);
362
465
  const { messages, pending, draft, awaiting } = live;
@@ -396,10 +499,7 @@ function Log({ live, name, words, classNames, components, locale, following, too
396
499
  observer.disconnect();
397
500
  };
398
501
  }, [following]);
399
- const idle = !onChannel && !pending && awaiting === null;
400
- const lastSaid = messages.findLastIndex((message) => message.role === "user");
401
- const answers = /* @__PURE__ */ new Map();
402
- for (const message of messages) if (message.action) answers.set(message.action.surface, message.action);
502
+ const { answers, current } = surfacesOf(live, onChannel);
403
503
  const handed = handovers === void 0 ? handoffs(messages) : NO_HANDOFFS;
404
504
  const placed = handoversAt(messages, handovers ?? []);
405
505
  const lines = (index) => placed.get(index)?.map((handover) => /* @__PURE__ */ jsx(HandoverLine, {
@@ -449,12 +549,16 @@ function Log({ live, name, words, classNames, components, locale, following, too
449
549
  message,
450
550
  from: carried.current?.id === message.id ? carried.current.from : void 0,
451
551
  awaiting: onChannel ? null : awaiting,
452
- current: idle && index > lastSaid,
552
+ current: current(index),
453
553
  answers,
454
554
  name,
455
555
  words,
456
556
  classNames,
457
557
  components,
558
+ blocks,
559
+ places,
560
+ opened,
561
+ onOpen,
458
562
  locale,
459
563
  onAction: live.act,
460
564
  onActAll: live.actAll,
@@ -679,7 +783,7 @@ function DeliveryMark({ message, words, classNames }) {
679
783
  * for (on a person's page, one the business allows is a quiet line instead); on a channel, where
680
784
  * it got to.
681
785
  */
682
- function Reply({ message, from, awaiting, current, answers, name, words, classNames, components, locale, onAction, builder, files, who, steps, onChannel, onActAll }) {
786
+ function Reply({ message, from, awaiting, current, answers, name, words, classNames, components, locale, onAction, builder, files, who, steps, onChannel, onActAll, blocks, places, opened, onOpen }) {
683
787
  const text = useReveal(message.text, from ?? message.text.length);
684
788
  const asked = message.steps.filter((step) => step.request !== null && !(step.request.decides === "business" && who === "person"));
685
789
  const { shown: said, typing } = onChannel ? channelParts(message, text) : {
@@ -727,24 +831,42 @@ function Reply({ message, from, awaiting, current, answers, name, words, classNa
727
831
  ] })
728
832
  }, step.id))
729
833
  }),
730
- message.steps.map((step) => step.surface && /* @__PURE__ */ jsx("div", {
731
- part: "surface",
732
- className: classes(classNames, "sc-chat__screen", ...framesItself(step.surface) ? ["sc-chat__screen--bare"] : []),
733
- children: /* @__PURE__ */ jsx(Surface, {
734
- surface: step.surface,
735
- active: step.id === awaiting || current,
736
- answered: answers.get(step.id),
737
- components,
738
- classNames,
739
- messages: words.kit,
740
- locale,
741
- files,
742
- onAction: (action) => onAction({
743
- surface: step.id,
744
- ...action
834
+ message.steps.map((step) => {
835
+ if (!step.surface) return null;
836
+ const place = placeOf(step.surface, places);
837
+ if (place !== "chat") {
838
+ const Glyph = place === "panel" ? SidebarSimpleIcon : ArrowsOutSimpleIcon;
839
+ return /* @__PURE__ */ jsxs("button", {
840
+ type: "button",
841
+ part: "placed",
842
+ "aria-expanded": opened === step.id,
843
+ onClick: () => {
844
+ onOpen(step.id);
845
+ },
846
+ className: classes(classNames, "sc-chat__placed", ...opened === step.id ? ["sc-chat__placed--open"] : []),
847
+ children: [/* @__PURE__ */ jsx(Glyph, { "aria-hidden": true }), /* @__PURE__ */ jsx("span", { children: words.opened(name, step.surface.title ?? "") })]
848
+ }, step.id);
849
+ }
850
+ return /* @__PURE__ */ jsx("div", {
851
+ part: "surface",
852
+ className: classes(classNames, "sc-chat__screen", ...framesItself(step.surface) ? ["sc-chat__screen--bare"] : []),
853
+ children: /* @__PURE__ */ jsx(Surface, {
854
+ surface: step.surface,
855
+ active: step.id === awaiting || current,
856
+ answered: answers.get(step.id),
857
+ components,
858
+ blocks,
859
+ classNames,
860
+ messages: words.kit,
861
+ locale,
862
+ files,
863
+ onAction: (action) => onAction({
864
+ surface: step.id,
865
+ ...action
866
+ })
745
867
  })
746
- })
747
- }, step.id)),
868
+ }, step.id);
869
+ }),
748
870
  asked.length > 1 && /* @__PURE__ */ jsx(Approvals, {
749
871
  steps: asked,
750
872
  name,
@@ -1274,6 +1396,6 @@ function Composer({ live, name, words, classNames, actions, compose, taken, fres
1274
1396
  });
1275
1397
  }
1276
1398
  //#endregion
1277
- export { AgentChat, CHAT_MESSAGES, decisionLine, handoffs, handoverBy, handoverLine, handoversAt, heardTail, shownState, whenSaid, workingOn };
1399
+ export { AgentChat, CHAT_MESSAGES, decisionLine, handoffs, handoverBy, handoverLine, handoversAt, heardTail, shownState, surfacesOf, whenSaid, workingOn };
1278
1400
 
1279
1401
  //# sourceMappingURL=AgentChat.js.map