@signalcore/ui 1.0.0-beta.1 → 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.
- package/README.md +55 -22
- package/dist/AgentAbout.js +11 -4
- package/dist/AgentAbout.js.map +1 -1
- package/dist/AgentChat.d.ts +7 -1
- package/dist/AgentChat.js +154 -2
- package/dist/AgentChat.js.map +1 -1
- package/dist/AgentSpace.d.ts +12 -3
- package/dist/AgentSpace.js +53 -11
- package/dist/AgentSpace.js.map +1 -1
- package/dist/AgentTasks.js +2 -1
- package/dist/AgentTasks.js.map +1 -1
- package/dist/Threads.d.ts +73 -0
- package/dist/Threads.js +140 -0
- package/dist/Threads.js.map +1 -0
- package/dist/VoiceCall.js +1 -1
- package/dist/VoiceCall.js.map +1 -1
- package/dist/about.d.ts +34 -144
- package/dist/about.js +6 -8
- package/dist/about.js.map +1 -1
- package/dist/automation/cron.d.ts +1 -1
- package/dist/automation/cron.js.map +1 -1
- package/dist/automation/marks.js +13 -5
- package/dist/automation/marks.js.map +1 -1
- package/dist/automation/messages.d.ts +6 -0
- package/dist/automation/messages.js +7 -1
- package/dist/automation/messages.js.map +1 -1
- package/dist/automation/types.d.ts +25 -128
- package/dist/automation/types.js +36 -0
- package/dist/automation/types.js.map +1 -0
- package/dist/automation/when.d.ts +1 -1
- package/dist/automation/when.js +13 -1
- package/dist/automation/when.js.map +1 -1
- package/dist/blocks.d.ts +23 -1
- package/dist/browser.js +2 -2
- package/dist/browser.js.map +1 -1
- package/dist/call.d.ts +1 -1
- package/dist/call.js.map +1 -1
- package/dist/client.d.ts +14 -4
- package/dist/client.js +7 -2
- package/dist/client.js.map +1 -1
- package/dist/contract.d.ts +267 -0
- package/dist/credentials.js.map +1 -1
- package/dist/dev.js +1 -1
- package/dist/dev.js.map +1 -1
- package/dist/es.d.ts +2 -0
- package/dist/es.js +31 -4
- package/dist/es.js.map +1 -1
- package/dist/frames.d.ts +7 -1
- package/dist/frames.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +6 -4
- package/dist/kit/AudioPlayer.js +1 -1
- package/dist/kit/AudioPlayer.js.map +1 -1
- package/dist/kit/Choices.js +1 -1
- package/dist/kit/Choices.js.map +1 -1
- package/dist/kit/FileField.js +1 -1
- package/dist/kit/FileField.js.map +1 -1
- package/dist/kit/Popover.js.map +1 -1
- package/dist/kit/Tile.d.ts +1 -1
- package/dist/kit/Tile.js +1 -1
- package/dist/kit/Tile.js.map +1 -1
- package/dist/kit/messages.d.ts +13 -0
- package/dist/kit/messages.js +7 -1
- package/dist/kit/messages.js.map +1 -1
- package/dist/kit/uploads.d.ts +2 -1
- package/dist/kit/uploads.js +1 -1
- package/dist/kit/uploads.js.map +1 -1
- package/dist/kit/waveform.js.map +1 -1
- package/dist/markdown.js.map +1 -1
- package/dist/marks.js +2 -3
- package/dist/marks.js.map +1 -1
- package/dist/surface/Form.js +1 -1
- package/dist/surface/Markdown.js +1 -1
- package/dist/surface/Markdown.js.map +1 -1
- package/dist/surface/Surface.js +43 -4
- package/dist/surface/Surface.js.map +1 -1
- package/dist/surface/scope.d.ts +1 -1
- package/dist/surface/scope.js.map +1 -1
- package/dist/surface/values.js +1 -1
- package/dist/surface/values.js.map +1 -1
- package/dist/tint.d.ts +1 -1
- package/dist/tint.js +1 -1
- package/dist/tint.js.map +1 -1
- package/dist/types.d.ts +11 -0
- package/dist/types.js.map +1 -1
- package/dist/useConversation.d.ts +2 -0
- package/dist/useConversation.js +16 -0
- package/dist/useConversation.js.map +1 -1
- package/dist/usePolled.js +1 -1
- package/dist/usePolled.js.map +1 -1
- package/dist/voice/webgl.js +1 -1
- package/dist/voice/webgl.js.map +1 -1
- package/dist/widget.js +17 -16
- package/package.json +1 -1
- package/src/AgentAbout.tsx +14 -3
- package/src/AgentChat.tsx +203 -1
- package/src/AgentSpace.tsx +89 -13
- package/src/AgentTasks.tsx +2 -2
- package/src/Threads.tsx +218 -0
- package/src/VoiceCall.tsx +1 -1
- package/src/about.ts +42 -144
- package/src/automation/cron.ts +1 -1
- package/src/automation/marks.tsx +13 -2
- package/src/automation/messages.ts +12 -0
- package/src/automation/types.ts +58 -118
- package/src/automation/when.ts +7 -1
- package/src/blocks.ts +25 -0
- package/src/browser.ts +4 -4
- package/src/call.ts +1 -1
- package/src/client.ts +26 -10
- package/src/contract.ts +1934 -0
- package/src/credentials.ts +2 -2
- package/src/dev.ts +1 -1
- package/src/es.ts +29 -0
- package/src/frames.ts +3 -1
- package/src/index.ts +10 -1
- package/src/kit/AudioPlayer.tsx +1 -1
- package/src/kit/Choices.tsx +1 -1
- package/src/kit/FileField.tsx +1 -1
- package/src/kit/Popover.tsx +1 -1
- package/src/kit/Tile.tsx +1 -1
- package/src/kit/messages.ts +20 -0
- package/src/kit/uploads.ts +2 -2
- package/src/kit/waveform.ts +1 -1
- package/src/markdown.ts +1 -1
- package/src/marks.tsx +3 -123
- package/src/styles.css +206 -0
- package/src/surface/Markdown.tsx +1 -1
- package/src/surface/Surface.tsx +53 -4
- package/src/surface/scope.ts +3 -1
- package/src/surface/values.ts +5 -5
- package/src/tint.ts +1 -1
- package/src/types.ts +11 -0
- package/src/useConversation.ts +10 -0
- package/src/usePolled.ts +2 -2
- package/src/voice/webgl.ts +1 -1
- package/src/widget/element.tsx +13 -4
- package/src/widget/widget.css +13 -0
package/dist/AgentSpace.js
CHANGED
|
@@ -7,6 +7,7 @@ import { agentTint } from "./tint.js";
|
|
|
7
7
|
import { AgentTasks, TASK_MESSAGES, useTasks } from "./AgentTasks.js";
|
|
8
8
|
import { useConversation } from "./useConversation.js";
|
|
9
9
|
import { AgentChat } from "./AgentChat.js";
|
|
10
|
+
import { THREAD_MESSAGES, Threads, useUnread } from "./Threads.js";
|
|
10
11
|
import { Modes } from "./Modes.js";
|
|
11
12
|
import { useAgentFace } from "./useAgentFace.js";
|
|
12
13
|
import { Suspense, lazy, useEffect, useRef, useState } from "react";
|
|
@@ -28,7 +29,8 @@ const SPACE_MESSAGES = {
|
|
|
28
29
|
retry: "Try again",
|
|
29
30
|
chat: {},
|
|
30
31
|
voice: {},
|
|
31
|
-
tasks: TASK_MESSAGES
|
|
32
|
+
tasks: TASK_MESSAGES,
|
|
33
|
+
threads: THREAD_MESSAGES
|
|
32
34
|
};
|
|
33
35
|
/** How big a call's orb is drawn in each layout: a small panel keeps room for what is said. */
|
|
34
36
|
const ORB_SIZES = {
|
|
@@ -49,13 +51,15 @@ const ORB_SIZES = {
|
|
|
49
51
|
* as `AgentChat` is: the tokens, `messages`, `classNames` and `components`. One agent per mount:
|
|
50
52
|
* key it on the agent's id.
|
|
51
53
|
*/
|
|
52
|
-
function AgentSpace({ client, agentId, conversationId, onConversation, layout = "compact", header = false, steps, messages, classNames, components, locale, suggestions, onTool, onError, orb, onClose, tasks: showsTasks = true }) {
|
|
54
|
+
function AgentSpace({ client, agentId, conversationId, onConversation, layout = "compact", header = false, steps, messages, classNames, components, locale, suggestions, onTool, onError, orb, onClose, tasks: showsTasks = true, threads: showsThreads = true }) {
|
|
53
55
|
const words = {
|
|
54
56
|
...SPACE_MESSAGES,
|
|
55
57
|
...messages
|
|
56
58
|
};
|
|
57
59
|
const theirs = useTasks(client, agentId, showsTasks && client.who === "person");
|
|
58
60
|
const [listing, setListing] = useState(false);
|
|
61
|
+
const threads = useUnread(client, agentId, showsThreads && client.who === "person");
|
|
62
|
+
const [browsing, setBrowsing] = useState(false);
|
|
59
63
|
const { face, loading } = useAgentFace(client, agentId);
|
|
60
64
|
const opened = useOpened(client, agentId, conversationId, onConversation);
|
|
61
65
|
const [mode, setMode] = useState("text");
|
|
@@ -108,6 +112,28 @@ function AgentSpace({ client, agentId, conversationId, onConversation, layout =
|
|
|
108
112
|
className: classes(classNames, "sc-space__status"),
|
|
109
113
|
children: words.opening
|
|
110
114
|
});
|
|
115
|
+
if (browsing && threads.threads !== null) {
|
|
116
|
+
const went = (next) => {
|
|
117
|
+
opened.go(next);
|
|
118
|
+
setBrowsing(false);
|
|
119
|
+
threads.refresh();
|
|
120
|
+
};
|
|
121
|
+
return /* @__PURE__ */ jsx(Threads, {
|
|
122
|
+
threads: threads.threads,
|
|
123
|
+
current: id,
|
|
124
|
+
onSelect: went,
|
|
125
|
+
onNew: () => {
|
|
126
|
+
client.conversations.start(agentId).then((started) => {
|
|
127
|
+
went(started.id);
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
onBack: () => {
|
|
131
|
+
setBrowsing(false);
|
|
132
|
+
},
|
|
133
|
+
messages: words.threads,
|
|
134
|
+
classNames
|
|
135
|
+
});
|
|
136
|
+
}
|
|
111
137
|
if (listing && theirs.tasks !== null) return /* @__PURE__ */ jsx(AgentTasks, {
|
|
112
138
|
client,
|
|
113
139
|
agentId,
|
|
@@ -165,15 +191,31 @@ function AgentSpace({ client, agentId, conversationId, onConversation, layout =
|
|
|
165
191
|
})]
|
|
166
192
|
});
|
|
167
193
|
const count = theirs.tasks?.length ?? 0;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
194
|
+
const news = (threads.threads ?? []).some((thread) => thread.id !== id && ((thread.unread ?? 0) > 0 || thread.waiting_on_person === true));
|
|
195
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [(threads.threads !== null || count > 0) && /* @__PURE__ */ jsxs("div", {
|
|
196
|
+
className: classes(classNames, "sc-space__bar"),
|
|
197
|
+
children: [threads.threads !== null && /* @__PURE__ */ jsxs("button", {
|
|
198
|
+
type: "button",
|
|
199
|
+
part: "threads-open",
|
|
200
|
+
className: classes(classNames, "sc-space__threads"),
|
|
201
|
+
onClick: () => {
|
|
202
|
+
setBrowsing(true);
|
|
203
|
+
threads.refresh();
|
|
204
|
+
},
|
|
205
|
+
children: [words.threads.open, news && /* @__PURE__ */ jsx("span", {
|
|
206
|
+
"aria-hidden": true,
|
|
207
|
+
className: classes(classNames, "sc-space__dot")
|
|
208
|
+
})]
|
|
209
|
+
}), count > 0 && /* @__PURE__ */ jsx("button", {
|
|
210
|
+
type: "button",
|
|
211
|
+
part: "tasks-open",
|
|
212
|
+
className: classes(classNames, "sc-space__tasks"),
|
|
213
|
+
onClick: () => {
|
|
214
|
+
setListing(true);
|
|
215
|
+
theirs.refresh();
|
|
216
|
+
},
|
|
217
|
+
children: words.tasks.open(count)
|
|
218
|
+
})]
|
|
177
219
|
}), /* @__PURE__ */ jsx(AgentChat, {
|
|
178
220
|
client,
|
|
179
221
|
conversationId: id,
|
package/dist/AgentSpace.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentSpace.js","names":[],"sources":["../src/AgentSpace.tsx"],"sourcesContent":["import { PhoneIcon, XIcon } from \"@phosphor-icons/react\";\nimport { lazy, Suspense, useEffect, useRef, useState } from \"react\";\n\nimport { AgentChat, type ChatMessages, type ChatPart, type ChatSteps } from \"./AgentChat\";\nimport { agentGlyph } from \"./agentIcons\";\nimport {\n AgentTasks,\n TASK_MESSAGES,\n useTasks,\n type TaskMessages,\n type TaskPart,\n} from \"./AgentTasks\";\nimport type { BrowserPart } from \"./BrowserView\";\nimport { classes } from \"./classes\";\nimport { ApiError, type Client } from \"./client\";\nimport { Button } from \"./kit/Button\";\nimport { KitProvider, type KitPart } from \"./kit/context\";\nimport type { KitLocale } from \"./kit/messages\";\nimport { Modes, type ModeMessages, type ModePart, type SpaceMode } from \"./Modes\";\nimport type { SurfaceComponents, SurfacePart } from \"./surface/scope\";\nimport { agentTint, type AgentTint } from \"./tint\";\nimport { useAgentFace } from \"./useAgentFace\";\nimport { useConversation, type LiveConversation, type ToolHandler } from \"./useConversation\";\nimport type { VoiceCallMessages, VoiceCallPart } from \"./VoiceCall\";\nimport type { OrbStyle } from \"./voice/orbs\";\n\n// The call, and LiveKit under it, load the first time someone calls.\nconst VoiceCall = lazy(() =>\n import(\"./VoiceCall\").then((module) => ({ default: module.VoiceCall })),\n);\n\n/** The words the space says around the chat and the call; a host gives its own, in its language. */\nexport interface SpaceMessages {\n /** The two modes beside the composer's paperclip: the group's name, and each mode's. */\n modes: ModeMessages;\n /** After a call that couldn't begin: try it again. */\n again: string;\n /** The button that closes the space, where the host gives `onClose`. */\n close: string;\n /** The agent's name when its face can't be read. */\n agent: string;\n /** While the conversation is found, or started. */\n opening: string;\n /** The conversation could not be opened; and the button that tries again. */\n failed: string;\n retry: string;\n /** What the chat says (`AgentChat`'s words). */\n chat: Partial<ChatMessages>;\n /** What a call says (`VoiceCall`'s words). */\n voice: Partial<VoiceCallMessages>;\n /** What the person's tasks say (`AgentTasks`'s words). */\n tasks: TaskMessages;\n}\n\nexport const SPACE_MESSAGES: SpaceMessages = {\n modes: { label: \"Mode\", text: \"Text\", voice: \"Voice\" },\n again: \"Call again\",\n close: \"Close\",\n agent: \"Agent\",\n opening: \"Opening the conversation…\",\n failed: \"The conversation couldn't be opened.\",\n retry: \"Try again\",\n chat: {},\n voice: {},\n tasks: TASK_MESSAGES,\n};\n\n/** The space's parts, by the class each carries; `classNames` adds a host's own under the same keys. */\nexport type SpacePart =\n | \"sc-space\"\n | \"sc-space--compact\"\n | \"sc-space--full\"\n | \"sc-space--voice\"\n | \"sc-space__face\"\n | \"sc-space__mark\"\n | `sc-space__mark--${AgentTint}`\n | \"sc-space__who\"\n | \"sc-space__name\"\n | \"sc-space__role\"\n | \"sc-space__close\"\n | \"sc-space__body\"\n | \"sc-space__voice\"\n | \"sc-space__foot\"\n | \"sc-space__caption\"\n | \"sc-space__caption--person\"\n | \"sc-space__caption--agent\"\n | \"sc-space__status\"\n | \"sc-space__tasks\"\n | ModePart;\n\n/**\n * How the space sits: `compact` in a small panel (a widget's, a side of the host's page); `full`\n * as a page's main column, the conversation at a reading measure.\n */\nexport type SpaceLayout = \"compact\" | \"full\";\n\n/** How big a call's orb is drawn in each layout: a small panel keeps room for what is said. */\nconst ORB_SIZES: Readonly<Record<SpaceLayout, number>> = { compact: 200, full: 280 };\n\nexport interface AgentSpaceProps {\n client: Client;\n /** The agent the person talks to (its face is read with `client`). One per mount: key on it. */\n agentId: string;\n /**\n * The person's conversation to go on with, read when the space opens. Without one, or once it\n * is gone (not theirs, not there), a new one starts, told through `onConversation`.\n */\n conversationId?: string | undefined;\n /** Hears the conversation the space started, for the host to keep and give back next time. */\n onConversation?: ((id: string) => void) | undefined;\n layout?: SpaceLayout | undefined;\n /**\n * Draws the agent's face at the top: its mark in its tint, its name and role (and the close).\n * Off by default: the host's page says who it is, or draws its own with `useAgentFace`.\n */\n header?: boolean | undefined;\n /**\n * Whether the chat shows the agent's steps (`AgentChat`'s `steps`): by default hidden for a\n * person or a visitor, shown for the business.\n */\n steps?: ChatSteps | undefined;\n messages?: Partial<SpaceMessages> | undefined;\n classNames?:\n | Partial<\n Record<\n SpacePart | ChatPart | SurfacePart | KitPart | BrowserPart | VoiceCallPart | TaskPart,\n string\n >\n >\n | undefined;\n /** The host's own drawing of a block type, for every surface the agent shows. */\n components?: SurfaceComponents | undefined;\n /** The language of the surfaces' calendars, days, times and numbers; English by default. */\n locale?: KitLocale | undefined;\n /** A few things the person might say first, offered while nothing has been said. */\n suggestions?: readonly string[] | undefined;\n /** Runs the host's own tools the agent calls in the page (`AgentChat`'s `onTool`). */\n onTool?: ToolHandler | undefined;\n /** An error's code, from the chat's socket or `load`; the host words it. */\n onError?: ((code: string) => void) | undefined;\n /** How a call's orb is drawn: `\"wave\"` (the default) or `\"fluid-dots\"`. */\n orb?: OrbStyle | undefined;\n /** Draws a close button (in the face, or at the top's end without it), for a space in a panel. */\n onClose?: (() => void) | undefined;\n /**\n * Whether a person sees the tasks the agent runs for them (`AgentTasks`): a line over the chat\n * while there are any, which opens them. On by default; the business's own space never has it.\n */\n tasks?: boolean | undefined;\n}\n\n/**\n * An agent in a host's page, for the people it serves: the conversation, and when the agent has a\n * voice, two modes beside the composer's paperclip, text and voice. Voice turns the space into\n * the call (`VoiceCall`, with the orb and what is being said), in the same place and size, with\n * the modes at its foot to go back; a call is a conversation of its own (the API makes one with\n * each), and once the agent took it the space goes on in it, where what was said shows, told\n * through `onConversation`. Ending the call, or choosing text, comes back to writing; a call that\n * couldn't begin stays to say why, and offers to try again. While the agent runs tasks for the\n * person (what they asked it to do later), a line over the chat opens them, with their runs, to\n * pause or take away. The conversation is the one given, or\n * one the space starts. The agent's face over it all is the host's to turn on (`header`). Styled\n * as `AgentChat` is: the tokens, `messages`, `classNames` and `components`. One agent per mount:\n * key it on the agent's id.\n */\nexport function AgentSpace({\n client,\n agentId,\n conversationId,\n onConversation,\n layout = \"compact\",\n header = false,\n steps,\n messages,\n classNames,\n components,\n locale,\n suggestions,\n onTool,\n onError,\n orb,\n onClose,\n tasks: showsTasks = true,\n}: AgentSpaceProps) {\n const words = { ...SPACE_MESSAGES, ...messages };\n const theirs = useTasks(client, agentId, showsTasks && client.who === \"person\");\n const [listing, setListing] = useState(false);\n const { face, loading } = useAgentFace(client, agentId);\n const opened = useOpened(client, agentId, conversationId, onConversation);\n const [mode, setMode] = useState<SpaceMode>(\"text\");\n // Each call made is a new attempt, which keys a new `VoiceCall`.\n const [attempt, setAttempt] = useState(0);\n // The conversation the call being made is (each call is one of its own, made with it), and\n // whether the agent took it: once it did, the space goes on in it.\n const [called, setCalled] = useState<string | null>(null);\n const answered = useRef(false);\n // Whether the modes drawn after a switch take the keyboard: when it was in the space (asked of\n // the space itself, which a shadow root's focus answers too).\n const [refocus, setRefocus] = useState(false);\n const root = useRef<HTMLElement>(null);\n const tint = agentTint(agentId);\n const Glyph = agentGlyph(face?.icon);\n const name = face?.name ?? (loading ? \"\" : words.agent);\n const id = opened.id;\n const voice = face?.voice.enabled === true;\n const parts: SpacePart[] = [`sc-space--${layout}`];\n if (mode === \"voice\") parts.push(\"sc-space--voice\");\n\n /** Back to writing: in the call's conversation once the agent took the call. */\n const write = () => {\n if (answered.current && called !== null) opened.go(called);\n setMode(\"text\");\n };\n const choose = (next: SpaceMode) => {\n setRefocus(root.current?.matches(\":focus-within\") ?? false);\n if (next === \"text\") {\n write();\n return;\n }\n answered.current = false;\n setCalled(null);\n setAttempt((count) => count + 1);\n setMode(\"voice\");\n };\n const modes = (current: SpaceMode) => (\n <Modes\n mode={current}\n onChange={choose}\n words={words.modes}\n classNames={classNames}\n focus={refocus}\n />\n );\n\n const body = () => {\n if (opened.failed) {\n return (\n <div className={classes(classNames, \"sc-space__status\")}>\n <p>{words.failed}</p>\n <Button size=\"sm\" onClick={opened.retry}>\n {words.retry}\n </Button>\n </div>\n );\n }\n if (id === null || loading) {\n return (\n <p role=\"status\" className={classes(classNames, \"sc-space__status\")}>\n {words.opening}\n </p>\n );\n }\n if (listing && theirs.tasks !== null) {\n return (\n <AgentTasks\n client={client}\n agentId={agentId}\n tasks={theirs.tasks}\n onChange={theirs.refresh}\n onBack={() => {\n setListing(false);\n }}\n words={words.tasks}\n classNames={classNames}\n />\n );\n }\n if (mode === \"voice\" && voice) {\n return (\n <div part=\"voice\" className={classes(classNames, \"sc-space__voice\")}>\n <Suspense fallback={null}>\n <VoiceCall\n key={attempt}\n connect={async () => {\n const join = await client.conversations.call(agentId);\n setCalled(join.conversation_id);\n return join;\n }}\n onAnswer={() => {\n answered.current = true;\n }}\n // Over, the call gives the space back to writing; one that couldn't begin stays to\n // say why, with the way to try again and the modes to go back.\n onEnd={(_, problem) => {\n if (problem !== null) return;\n setRefocus(root.current?.matches(\":focus-within\") ?? false);\n write();\n }}\n name={name}\n tint={tint}\n orb={orb}\n orbSize={ORB_SIZES[layout]}\n messages={words.voice}\n classNames={classNames}\n captions={\n called !== null && (\n <Captions\n client={client}\n conversationId={called}\n onTool={onTool}\n classNames={classNames}\n />\n )\n }\n after={\n <Button\n tone=\"primary\"\n icon={PhoneIcon}\n onClick={() => {\n choose(\"voice\");\n }}\n >\n {words.again}\n </Button>\n }\n />\n </Suspense>\n <div className={classes(classNames, \"sc-space__foot\")}>{modes(\"voice\")}</div>\n </div>\n );\n }\n const count = theirs.tasks?.length ?? 0;\n return (\n <>\n {count > 0 && (\n <button\n type=\"button\"\n part=\"tasks-open\"\n className={classes(classNames, \"sc-space__tasks\")}\n onClick={() => {\n setListing(true);\n void theirs.refresh();\n }}\n >\n {words.tasks.open(count)}\n </button>\n )}\n <AgentChat\n client={client}\n conversationId={id}\n name={name}\n messages={words.chat}\n classNames={classNames}\n components={components}\n locale={locale}\n suggestions={suggestions}\n steps={steps}\n actions={voice ? modes(\"text\") : undefined}\n onTool={onTool}\n onError={onError}\n />\n </>\n );\n };\n\n const close = onClose && (\n <Button\n tone=\"ghost\"\n size=\"sm\"\n icon={XIcon}\n aria-label={words.close}\n title={words.close}\n onClick={onClose}\n part=\"close\"\n className={classes(classNames, \"sc-space__close\")}\n />\n );\n\n return (\n <KitProvider classNames={classNames} messages={words.chat.kit} locale={locale}>\n <section\n ref={root}\n aria-label={name || undefined}\n part=\"space\"\n className={`sc ${classes(classNames, \"sc-space\", ...parts)}`}\n >\n {header ? (\n <header part=\"header\" className={classes(classNames, \"sc-space__face\")}>\n <span\n aria-hidden\n className={classes(classNames, \"sc-space__mark\", `sc-space__mark--${tint}`)}\n >\n <Glyph weight=\"duotone\" />\n </span>\n <div className={classes(classNames, \"sc-space__who\")}>\n <h2 className={classes(classNames, \"sc-space__name\")}>{name}</h2>\n {face?.role && <p className={classes(classNames, \"sc-space__role\")}>{face.role}</p>}\n </div>\n {close}\n </header>\n ) : (\n close\n )}\n <div className={classes(classNames, \"sc-space__body\")}>{body()}</div>\n </section>\n </KitProvider>\n );\n}\n\n/**\n * What is being said on the call, under the orb: the person's last words, and the agent's answer\n * as it comes, read live from the call's own conversation (where the platform keeps them). The\n * host's tools the agent calls meanwhile run here too, as in the chat.\n */\nfunction Captions({\n client,\n conversationId,\n onTool,\n classNames,\n}: {\n client: Client;\n conversationId: string;\n onTool: ToolHandler | undefined;\n classNames: Partial<Record<SpacePart, string>> | undefined;\n}) {\n const live = useConversation(client, conversationId, { onTool });\n const { person, agent } = captionsOf(live);\n return (\n <>\n {person !== \"\" && (\n <p className={classes(classNames, \"sc-space__caption\", \"sc-space__caption--person\")}>\n {person}\n </p>\n )}\n {agent !== \"\" && (\n <p className={classes(classNames, \"sc-space__caption\", \"sc-space__caption--agent\")}>\n {agent}\n </p>\n )}\n </>\n );\n}\n\n/**\n * The last exchange of a call, as its captions say it: what the person last said, and the agent's\n * words after it (as they stream, else the last it said; its greeting before anything was said).\n */\nexport function captionsOf({\n messages,\n pending,\n draft,\n}: Pick<LiveConversation, \"messages\" | \"pending\" | \"draft\">): {\n person: string;\n agent: string;\n} {\n const lastSaid = messages.findLastIndex((message) => message.role === \"user\");\n const replied = messages\n .slice(lastSaid + 1)\n .findLast(\n (message) =>\n message.role === \"assistant\" && message.author !== \"operator\" && message.text !== \"\",\n );\n return {\n person: messages[lastSaid]?.text ?? \"\",\n agent: pending && draft !== \"\" ? draft : (replied?.text ?? \"\"),\n };\n}\n\n/** Whether a conversation asked for is gone for this person: not there, or not theirs. */\nconst isGone = (error: unknown): boolean =>\n error instanceof ApiError && (error.status === 404 || error.status === 403);\n\n/**\n * The conversation the space shows: the one given while it is still the person's, else a new one\n * with the agent, told to `onConversation`. Asked once per attempt (a second effect of the same\n * attempt, as React's strict mode runs, waits on the first rather than starting another); after\n * that, the conversation it opened is the one it goes on with, until it goes on in another (`go`:\n * a call's), told the same way.\n */\nfunction useOpened(\n client: Client,\n agentId: string,\n given: string | undefined,\n onConversation: ((id: string) => void) | undefined,\n): { id: string | null; failed: boolean; retry: () => void; go: (id: string) => void } {\n const kept = useRef(given);\n const asking = useRef<{ client: Client; attempt: number; id: Promise<string> } | null>(null);\n const [id, setId] = useState<string | null>(null);\n const [failed, setFailed] = useState(false);\n const [attempt, setAttempt] = useState(0);\n // The host's ear as it last gave it, told from the effect's answer and from a call's end alike.\n const told = useRef(onConversation);\n useEffect(() => {\n told.current = onConversation;\n });\n const started = (conversation: string) => {\n told.current?.(conversation);\n };\n\n useEffect(() => {\n let current = true;\n const start = () =>\n client.conversations.start(agentId).then((conversation) => {\n started(conversation.id);\n return conversation.id;\n });\n const open = (): Promise<string> => {\n const known = kept.current;\n if (known === undefined) return start();\n return client.conversations.get(known).then(\n (conversation) => conversation.id,\n (error: unknown) => (isGone(error) ? start() : Promise.reject(error)),\n );\n };\n const asked = asking.current;\n const ask =\n asked?.client === client && asked.attempt === attempt\n ? asked\n : { client, attempt, id: open() };\n asking.current = ask;\n ask.id.then(\n (opened) => {\n if (!current) return;\n kept.current = opened;\n setId(opened);\n },\n () => {\n if (current) setFailed(true);\n },\n );\n return () => {\n current = false;\n };\n }, [client, agentId, attempt]);\n\n return {\n id,\n failed,\n retry: () => {\n setFailed(false);\n setAttempt((count) => count + 1);\n },\n go: (conversation) => {\n if (conversation === kept.current) return;\n kept.current = conversation;\n setId(conversation);\n started(conversation);\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AA2BA,MAAM,YAAY,WAChB,OAAO,iBAAc,CAAC,MAAM,YAAY,EAAE,SAAS,OAAO,UAAU,EAAE,CACxE;AAyBA,MAAa,iBAAgC;CAC3C,OAAO;EAAE,OAAO;EAAQ,MAAM;EAAQ,OAAO;CAAQ;CACrD,OAAO;CACP,OAAO;CACP,OAAO;CACP,SAAS;CACT,QAAQ;CACR,OAAO;CACP,MAAM,CAAC;CACP,OAAO,CAAC;CACR,OAAO;AACT;;AAgCA,MAAM,YAAmD;CAAE,SAAS;CAAK,MAAM;AAAI;;;;;;;;;;;;;;;AAoEnF,SAAgB,WAAW,EACzB,QACA,SACA,gBACA,gBACA,SAAS,WACT,SAAS,OACT,OACA,UACA,YACA,YACA,QACA,aACA,QACA,SACA,KACA,SACA,OAAO,aAAa,QACF;CAClB,MAAM,QAAQ;EAAE,GAAG;EAAgB,GAAG;CAAS;CAC/C,MAAM,SAAS,SAAS,QAAQ,SAAS,cAAc,OAAO,QAAQ,QAAQ;CAC9E,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,EAAE,MAAM,YAAY,aAAa,QAAQ,OAAO;CACtD,MAAM,SAAS,UAAU,QAAQ,SAAS,gBAAgB,cAAc;CACxE,MAAM,CAAC,MAAM,WAAW,SAAoB,MAAM;CAElD,MAAM,CAAC,SAAS,cAAc,SAAS,CAAC;CAGxC,MAAM,CAAC,QAAQ,aAAa,SAAwB,IAAI;CACxD,MAAM,WAAW,OAAO,KAAK;CAG7B,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,OAAO,OAAoB,IAAI;CACrC,MAAM,OAAO,UAAU,OAAO;CAC9B,MAAM,QAAQ,WAAW,MAAM,IAAI;CACnC,MAAM,OAAO,MAAM,SAAS,UAAU,KAAK,MAAM;CACjD,MAAM,KAAK,OAAO;CAClB,MAAM,QAAQ,MAAM,MAAM,YAAY;CACtC,MAAM,QAAqB,CAAC,aAAa,QAAQ;CACjD,IAAI,SAAS,SAAS,MAAM,KAAK,iBAAiB;;CAGlD,MAAM,cAAc;EAClB,IAAI,SAAS,WAAW,WAAW,MAAM,OAAO,GAAG,MAAM;EACzD,QAAQ,MAAM;CAChB;CACA,MAAM,UAAU,SAAoB;EAClC,WAAW,KAAK,SAAS,QAAQ,eAAe,KAAK,KAAK;EAC1D,IAAI,SAAS,QAAQ;GACnB,MAAM;GACN;EACF;EACA,SAAS,UAAU;EACnB,UAAU,IAAI;EACd,YAAY,UAAU,QAAQ,CAAC;EAC/B,QAAQ,OAAO;CACjB;CACA,MAAM,SAAS,YACb,oBAAC,OAAD;EACE,MAAM;EACN,UAAU;EACV,OAAO,MAAM;EACD;EACZ,OAAO;CACR,CAAA;CAGH,MAAM,aAAa;EACjB,IAAI,OAAO,QACT,OACE,qBAAC,OAAD;GAAK,WAAW,QAAQ,YAAY,kBAAkB;GAAtD,UAAA,CACE,oBAAC,KAAD,EAAA,UAAI,MAAM,OAAU,CAAA,GACpB,oBAAC,QAAD;IAAQ,MAAK;IAAK,SAAS,OAAO;IAC/B,UAAA,MAAM;GACD,CAAA,CACL;;EAGT,IAAI,OAAO,QAAQ,SACjB,OACE,oBAAC,KAAD;GAAG,MAAK;GAAS,WAAW,QAAQ,YAAY,kBAAkB;GAC/D,UAAA,MAAM;EACN,CAAA;EAGP,IAAI,WAAW,OAAO,UAAU,MAC9B,OACE,oBAAC,YAAD;GACU;GACC;GACT,OAAO,OAAO;GACd,UAAU,OAAO;GACjB,cAAc;IACZ,WAAW,KAAK;GAClB;GACA,OAAO,MAAM;GACD;EACb,CAAA;EAGL,IAAI,SAAS,WAAW,OACtB,OACE,qBAAC,OAAD;GAAK,MAAK;GAAQ,WAAW,QAAQ,YAAY,iBAAiB;GAAlE,UAAA,CACE,oBAAC,UAAD;IAAU,UAAU;IAClB,UAAA,oBAAC,WAAD;KAEE,SAAS,YAAY;MACnB,MAAM,OAAO,MAAM,OAAO,cAAc,KAAK,OAAO;MACpD,UAAU,KAAK,eAAe;MAC9B,OAAO;KACT;KACA,gBAAgB;MACd,SAAS,UAAU;KACrB;KAGA,QAAQ,GAAG,YAAY;MACrB,IAAI,YAAY,MAAM;MACtB,WAAW,KAAK,SAAS,QAAQ,eAAe,KAAK,KAAK;MAC1D,MAAM;KACR;KACM;KACA;KACD;KACL,SAAS,UAAU;KACnB,UAAU,MAAM;KACJ;KACZ,UACE,WAAW,QACT,oBAAC,UAAD;MACU;MACR,gBAAgB;MACR;MACI;KACb,CAAA;KAGL,OACE,oBAAC,QAAD;MACE,MAAK;MACL,MAAM;MACN,eAAe;OACb,OAAO,OAAO;MAChB;MAEC,UAAA,MAAM;KACD,CAAA;IAEX,GA3CM,OA2CN;GACO,CAAA,GACV,oBAAC,OAAD;IAAK,WAAW,QAAQ,YAAY,gBAAgB;IAAI,UAAA,MAAM,OAAO;GAAO,CAAA,CACzE;;EAGT,MAAM,QAAQ,OAAO,OAAO,UAAU;EACtC,OACE,qBAAA,YAAA,EAAA,UAAA,CACG,QAAQ,KACP,oBAAC,UAAD;GACE,MAAK;GACL,MAAK;GACL,WAAW,QAAQ,YAAY,iBAAiB;GAChD,eAAe;IACb,WAAW,IAAI;IACf,OAAY,QAAQ;GACtB;GAEC,UAAA,MAAM,MAAM,KAAK,KAAK;EACjB,CAAA,GAEV,oBAAC,WAAD;GACU;GACR,gBAAgB;GACV;GACN,UAAU,MAAM;GACJ;GACA;GACJ;GACK;GACN;GACP,SAAS,QAAQ,MAAM,MAAM,IAAI,KAAA;GACzB;GACC;EACV,CAAA,CACD,EAAA,CAAA;CAEN;CAEA,MAAM,QAAQ,WACZ,oBAAC,QAAD;EACE,MAAK;EACL,MAAK;EACL,MAAM;EACN,cAAY,MAAM;EAClB,OAAO,MAAM;EACb,SAAS;EACT,MAAK;EACL,WAAW,QAAQ,YAAY,iBAAiB;CACjD,CAAA;CAGH,OACE,oBAAC,aAAD;EAAyB;EAAY,UAAU,MAAM,KAAK;EAAa;EACrE,UAAA,qBAAC,WAAD;GACE,KAAK;GACL,cAAY,QAAQ,KAAA;GACpB,MAAK;GACL,WAAW,MAAM,QAAQ,YAAY,YAAY,GAAG,KAAK;GAJ3D,UAAA,CAMG,SACC,qBAAC,UAAD;IAAQ,MAAK;IAAS,WAAW,QAAQ,YAAY,gBAAgB;IAArE,UAAA;KACE,oBAAC,QAAD;MACE,eAAA;MACA,WAAW,QAAQ,YAAY,kBAAkB,mBAAmB,MAAM;MAE1E,UAAA,oBAAC,OAAD,EAAO,QAAO,UAAW,CAAA;KACrB,CAAA;KACN,qBAAC,OAAD;MAAK,WAAW,QAAQ,YAAY,eAAe;MAAnD,UAAA,CACE,oBAAC,MAAD;OAAI,WAAW,QAAQ,YAAY,gBAAgB;OAAI,UAAA;MAAS,CAAA,GAC/D,MAAM,QAAQ,oBAAC,KAAD;OAAG,WAAW,QAAQ,YAAY,gBAAgB;OAAI,UAAA,KAAK;MAAQ,CAAA,CAC/E;;KACJ;IACK;GAER,CAAA,IAAA,OAEF,oBAAC,OAAD;IAAK,WAAW,QAAQ,YAAY,gBAAgB;IAAI,UAAA,KAAK;GAAO,CAAA,CAC7D;;CACE,CAAA;AAEjB;;;;;;AAOA,SAAS,SAAS,EAChB,QACA,gBACA,QACA,cAMC;CAED,MAAM,EAAE,QAAQ,UAAU,WADb,gBAAgB,QAAQ,gBAAgB,EAAE,OAAO,CACtB,CAAC;CACzC,OACE,qBAAA,YAAA,EAAA,UAAA,CACG,WAAW,MACV,oBAAC,KAAD;EAAG,WAAW,QAAQ,YAAY,qBAAqB,2BAA2B;EAC/E,UAAA;CACA,CAAA,GAEJ,UAAU,MACT,oBAAC,KAAD;EAAG,WAAW,QAAQ,YAAY,qBAAqB,0BAA0B;EAC9E,UAAA;CACA,CAAA,CAEL,EAAA,CAAA;AAEN;;;;;AAMA,SAAgB,WAAW,EACzB,UACA,SACA,SAIA;CACA,MAAM,WAAW,SAAS,eAAe,YAAY,QAAQ,SAAS,MAAM;CAC5E,MAAM,UAAU,SACb,MAAM,WAAW,CAAC,CAAC,CACnB,UACE,YACC,QAAQ,SAAS,eAAe,QAAQ,WAAW,cAAc,QAAQ,SAAS,EACtF;CACF,OAAO;EACL,QAAQ,SAAS,SAAS,EAAE,QAAQ;EACpC,OAAO,WAAW,UAAU,KAAK,QAAS,SAAS,QAAQ;CAC7D;AACF;;AAGA,MAAM,UAAU,UACd,iBAAiB,aAAa,MAAM,WAAW,OAAO,MAAM,WAAW;;;;;;;;AASzE,SAAS,UACP,QACA,SACA,OACA,gBACqF;CACrF,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,SAAS,OAAwE,IAAI;CAC3F,MAAM,CAAC,IAAI,SAAS,SAAwB,IAAI;CAChD,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAC1C,MAAM,CAAC,SAAS,cAAc,SAAS,CAAC;CAExC,MAAM,OAAO,OAAO,cAAc;CAClC,gBAAgB;EACd,KAAK,UAAU;CACjB,CAAC;CACD,MAAM,WAAW,iBAAyB;EACxC,KAAK,UAAU,YAAY;CAC7B;CAEA,gBAAgB;EACd,IAAI,UAAU;EACd,MAAM,cACJ,OAAO,cAAc,MAAM,OAAO,CAAC,CAAC,MAAM,iBAAiB;GACzD,QAAQ,aAAa,EAAE;GACvB,OAAO,aAAa;EACtB,CAAC;EACH,MAAM,aAA8B;GAClC,MAAM,QAAQ,KAAK;GACnB,IAAI,UAAU,KAAA,GAAW,OAAO,MAAM;GACtC,OAAO,OAAO,cAAc,IAAI,KAAK,CAAC,CAAC,MACpC,iBAAiB,aAAa,KAC9B,UAAoB,OAAO,KAAK,IAAI,MAAM,IAAI,QAAQ,OAAO,KAAK,CACrE;EACF;EACA,MAAM,QAAQ,OAAO;EACrB,MAAM,MACJ,OAAO,WAAW,UAAU,MAAM,YAAY,UAC1C,QACA;GAAE;GAAQ;GAAS,IAAI,KAAK;EAAE;EACpC,OAAO,UAAU;EACjB,IAAI,GAAG,MACJ,WAAW;GACV,IAAI,CAAC,SAAS;GACd,KAAK,UAAU;GACf,MAAM,MAAM;EACd,SACM;GACJ,IAAI,SAAS,UAAU,IAAI;EAC7B,CACF;EACA,aAAa;GACX,UAAU;EACZ;CACF,GAAG;EAAC;EAAQ;EAAS;CAAO,CAAC;CAE7B,OAAO;EACL;EACA;EACA,aAAa;GACX,UAAU,KAAK;GACf,YAAY,UAAU,QAAQ,CAAC;EACjC;EACA,KAAK,iBAAiB;GACpB,IAAI,iBAAiB,KAAK,SAAS;GACnC,KAAK,UAAU;GACf,MAAM,YAAY;GAClB,QAAQ,YAAY;EACtB;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"AgentSpace.js","names":[],"sources":["../src/AgentSpace.tsx"],"sourcesContent":["import { PhoneIcon, XIcon } from \"@phosphor-icons/react\";\nimport { lazy, Suspense, useEffect, useRef, useState } from \"react\";\n\nimport { AgentChat, type ChatMessages, type ChatPart, type ChatSteps } from \"./AgentChat\";\nimport { agentGlyph } from \"./agentIcons\";\nimport {\n AgentTasks,\n TASK_MESSAGES,\n useTasks,\n type TaskMessages,\n type TaskPart,\n} from \"./AgentTasks\";\nimport type { BrowserPart } from \"./BrowserView\";\nimport { classes } from \"./classes\";\nimport { ApiError, type Client } from \"./client\";\nimport { Button } from \"./kit/Button\";\nimport { KitProvider, type KitPart } from \"./kit/context\";\nimport type { KitLocale } from \"./kit/messages\";\nimport { Modes, type ModeMessages, type ModePart, type SpaceMode } from \"./Modes\";\nimport type { SurfaceComponents, SurfacePart } from \"./surface/scope\";\nimport { agentTint, type AgentTint } from \"./tint\";\nimport {\n THREAD_MESSAGES,\n Threads,\n useUnread,\n type ThreadMessages,\n type ThreadPart,\n} from \"./Threads\";\nimport { useAgentFace } from \"./useAgentFace\";\nimport { useConversation, type LiveConversation, type ToolHandler } from \"./useConversation\";\nimport type { VoiceCallMessages, VoiceCallPart } from \"./VoiceCall\";\nimport type { OrbStyle } from \"./voice/orbs\";\n\n// The call, and LiveKit under it, load the first time someone calls.\nconst VoiceCall = lazy(() =>\n import(\"./VoiceCall\").then((module) => ({ default: module.VoiceCall })),\n);\n\n/** The words the space says around the chat and the call; a host gives its own, in its language. */\nexport interface SpaceMessages {\n /** The two modes beside the composer's paperclip: the group's name, and each mode's. */\n modes: ModeMessages;\n /** After a call that couldn't begin: try it again. */\n again: string;\n /** The button that closes the space, where the host gives `onClose`. */\n close: string;\n /** The agent's name when its face can't be read. */\n agent: string;\n /** While the conversation is found, or started. */\n opening: string;\n /** The conversation could not be opened; and the button that tries again. */\n failed: string;\n retry: string;\n /** What the chat says (`AgentChat`'s words). */\n chat: Partial<ChatMessages>;\n /** What a call says (`VoiceCall`'s words). */\n voice: Partial<VoiceCallMessages>;\n /** What the person's tasks say (`AgentTasks`'s words). */\n tasks: TaskMessages;\n /** What the person's threads say (`Threads`'s words). */\n threads: ThreadMessages;\n}\n\nexport const SPACE_MESSAGES: SpaceMessages = {\n modes: { label: \"Mode\", text: \"Text\", voice: \"Voice\" },\n again: \"Call again\",\n close: \"Close\",\n agent: \"Agent\",\n opening: \"Opening the conversation…\",\n failed: \"The conversation couldn't be opened.\",\n retry: \"Try again\",\n chat: {},\n voice: {},\n tasks: TASK_MESSAGES,\n threads: THREAD_MESSAGES,\n};\n\n/** The space's parts, by the class each carries; `classNames` adds a host's own under the same keys. */\nexport type SpacePart =\n | \"sc-space\"\n | \"sc-space--compact\"\n | \"sc-space--full\"\n | \"sc-space--voice\"\n | \"sc-space__face\"\n | \"sc-space__mark\"\n | `sc-space__mark--${AgentTint}`\n | \"sc-space__who\"\n | \"sc-space__name\"\n | \"sc-space__role\"\n | \"sc-space__close\"\n | \"sc-space__body\"\n | \"sc-space__voice\"\n | \"sc-space__foot\"\n | \"sc-space__caption\"\n | \"sc-space__caption--person\"\n | \"sc-space__caption--agent\"\n | \"sc-space__status\"\n | \"sc-space__tasks\"\n | \"sc-space__bar\"\n | \"sc-space__threads\"\n | \"sc-space__dot\"\n | ModePart;\n\n/**\n * How the space sits: `compact` in a small panel (a widget's, a side of the host's page); `full`\n * as a page's main column, the conversation at a reading measure.\n */\nexport type SpaceLayout = \"compact\" | \"full\";\n\n/** How big a call's orb is drawn in each layout: a small panel keeps room for what is said. */\nconst ORB_SIZES: Readonly<Record<SpaceLayout, number>> = { compact: 200, full: 280 };\n\nexport interface AgentSpaceProps {\n client: Client;\n /** The agent the person talks to (its face is read with `client`). One per mount: key on it. */\n agentId: string;\n /**\n * The person's conversation to go on with, read when the space opens. Without one, or once it\n * is gone (not theirs, not there), a new one starts, told through `onConversation`.\n */\n conversationId?: string | undefined;\n /** Hears the conversation the space started, for the host to keep and give back next time. */\n onConversation?: ((id: string) => void) | undefined;\n layout?: SpaceLayout | undefined;\n /**\n * Draws the agent's face at the top: its mark in its tint, its name and role (and the close).\n * Off by default: the host's page says who it is, or draws its own with `useAgentFace`.\n */\n header?: boolean | undefined;\n /**\n * Whether the chat shows the agent's steps (`AgentChat`'s `steps`): by default hidden for a\n * person or a visitor, shown for the business.\n */\n steps?: ChatSteps | undefined;\n messages?: Partial<SpaceMessages> | undefined;\n classNames?:\n | Partial<\n Record<\n | SpacePart\n | ChatPart\n | SurfacePart\n | KitPart\n | BrowserPart\n | VoiceCallPart\n | TaskPart\n | ThreadPart,\n string\n >\n >\n | undefined;\n /** The host's own drawing of a block type, for every surface the agent shows. */\n components?: SurfaceComponents | undefined;\n /** The language of the surfaces' calendars, days, times and numbers; English by default. */\n locale?: KitLocale | undefined;\n /** A few things the person might say first, offered while nothing has been said. */\n suggestions?: readonly string[] | undefined;\n /** Runs the host's own tools the agent calls in the page (`AgentChat`'s `onTool`). */\n onTool?: ToolHandler | undefined;\n /** An error's code, from the chat's socket or `load`; the host words it. */\n onError?: ((code: string) => void) | undefined;\n /** How a call's orb is drawn: `\"wave\"` (the default) or `\"fluid-dots\"`. */\n orb?: OrbStyle | undefined;\n /** Draws a close button (in the face, or at the top's end without it), for a space in a panel. */\n onClose?: (() => void) | undefined;\n /**\n * Whether a person sees the tasks the agent runs for them (`AgentTasks`): a line over the chat\n * while there are any, which opens them. On by default; the business's own space never has it.\n */\n tasks?: boolean | undefined;\n /**\n * Whether a person sees their threads with the agent (`Threads`): a line over the chat, a dot on\n * it while another waits on them, which lists them by title with what is unread, to go to one\n * or start another. On by default; the business's own space never has it.\n */\n threads?: boolean | undefined;\n}\n\n/**\n * An agent in a host's page, for the people it serves: the conversation, and when the agent has a\n * voice, two modes beside the composer's paperclip, text and voice. Voice turns the space into\n * the call (`VoiceCall`, with the orb and what is being said), in the same place and size, with\n * the modes at its foot to go back; a call is a conversation of its own (the API makes one with\n * each), and once the agent took it the space goes on in it, where what was said shows, told\n * through `onConversation`. Ending the call, or choosing text, comes back to writing; a call that\n * couldn't begin stays to say why, and offers to try again. While the agent runs tasks for the\n * person (what they asked it to do later), a line over the chat opens them, with their runs, to\n * pause or take away. The conversation is the one given, or\n * one the space starts. The agent's face over it all is the host's to turn on (`header`). Styled\n * as `AgentChat` is: the tokens, `messages`, `classNames` and `components`. One agent per mount:\n * key it on the agent's id.\n */\nexport function AgentSpace({\n client,\n agentId,\n conversationId,\n onConversation,\n layout = \"compact\",\n header = false,\n steps,\n messages,\n classNames,\n components,\n locale,\n suggestions,\n onTool,\n onError,\n orb,\n onClose,\n tasks: showsTasks = true,\n threads: showsThreads = true,\n}: AgentSpaceProps) {\n const words = { ...SPACE_MESSAGES, ...messages };\n const theirs = useTasks(client, agentId, showsTasks && client.who === \"person\");\n const [listing, setListing] = useState(false);\n const threads = useUnread(client, agentId, showsThreads && client.who === \"person\");\n const [browsing, setBrowsing] = useState(false);\n const { face, loading } = useAgentFace(client, agentId);\n const opened = useOpened(client, agentId, conversationId, onConversation);\n const [mode, setMode] = useState<SpaceMode>(\"text\");\n // Each call made is a new attempt, which keys a new `VoiceCall`.\n const [attempt, setAttempt] = useState(0);\n // The conversation the call being made is (each call is one of its own, made with it), and\n // whether the agent took it: once it did, the space goes on in it.\n const [called, setCalled] = useState<string | null>(null);\n const answered = useRef(false);\n // Whether the modes drawn after a switch take the keyboard: when it was in the space (asked of\n // the space itself, which a shadow root's focus answers too).\n const [refocus, setRefocus] = useState(false);\n const root = useRef<HTMLElement>(null);\n const tint = agentTint(agentId);\n const Glyph = agentGlyph(face?.icon);\n const name = face?.name ?? (loading ? \"\" : words.agent);\n const id = opened.id;\n const voice = face?.voice.enabled === true;\n const parts: SpacePart[] = [`sc-space--${layout}`];\n if (mode === \"voice\") parts.push(\"sc-space--voice\");\n\n /** Back to writing: in the call's conversation once the agent took the call. */\n const write = () => {\n if (answered.current && called !== null) opened.go(called);\n setMode(\"text\");\n };\n const choose = (next: SpaceMode) => {\n setRefocus(root.current?.matches(\":focus-within\") ?? false);\n if (next === \"text\") {\n write();\n return;\n }\n answered.current = false;\n setCalled(null);\n setAttempt((count) => count + 1);\n setMode(\"voice\");\n };\n const modes = (current: SpaceMode) => (\n <Modes\n mode={current}\n onChange={choose}\n words={words.modes}\n classNames={classNames}\n focus={refocus}\n />\n );\n\n const body = () => {\n if (opened.failed) {\n return (\n <div className={classes(classNames, \"sc-space__status\")}>\n <p>{words.failed}</p>\n <Button size=\"sm\" onClick={opened.retry}>\n {words.retry}\n </Button>\n </div>\n );\n }\n if (id === null || loading) {\n return (\n <p role=\"status\" className={classes(classNames, \"sc-space__status\")}>\n {words.opening}\n </p>\n );\n }\n if (browsing && threads.threads !== null) {\n const went = (next: string) => {\n opened.go(next);\n setBrowsing(false);\n void threads.refresh();\n };\n return (\n <Threads\n threads={threads.threads}\n current={id}\n onSelect={went}\n onNew={() => {\n void client.conversations.start(agentId).then((started) => {\n went(started.id);\n });\n }}\n onBack={() => {\n setBrowsing(false);\n }}\n messages={words.threads}\n classNames={classNames}\n />\n );\n }\n if (listing && theirs.tasks !== null) {\n return (\n <AgentTasks\n client={client}\n agentId={agentId}\n tasks={theirs.tasks}\n onChange={theirs.refresh}\n onBack={() => {\n setListing(false);\n }}\n words={words.tasks}\n classNames={classNames}\n />\n );\n }\n if (mode === \"voice\" && voice) {\n return (\n <div part=\"voice\" className={classes(classNames, \"sc-space__voice\")}>\n <Suspense fallback={null}>\n <VoiceCall\n key={attempt}\n connect={async () => {\n const join = await client.conversations.call(agentId);\n setCalled(join.conversation_id);\n return join;\n }}\n onAnswer={() => {\n answered.current = true;\n }}\n // Over, the call gives the space back to writing; one that couldn't begin stays to\n // say why, with the way to try again and the modes to go back.\n onEnd={(_, problem) => {\n if (problem !== null) return;\n setRefocus(root.current?.matches(\":focus-within\") ?? false);\n write();\n }}\n name={name}\n tint={tint}\n orb={orb}\n orbSize={ORB_SIZES[layout]}\n messages={words.voice}\n classNames={classNames}\n captions={\n called !== null && (\n <Captions\n client={client}\n conversationId={called}\n onTool={onTool}\n classNames={classNames}\n />\n )\n }\n after={\n <Button\n tone=\"primary\"\n icon={PhoneIcon}\n onClick={() => {\n choose(\"voice\");\n }}\n >\n {words.again}\n </Button>\n }\n />\n </Suspense>\n <div className={classes(classNames, \"sc-space__foot\")}>{modes(\"voice\")}</div>\n </div>\n );\n }\n const count = theirs.tasks?.length ?? 0;\n // Another thread with something for them: the line to the threads carries a dot.\n const news = (threads.threads ?? []).some(\n (thread) =>\n thread.id !== id && ((thread.unread ?? 0) > 0 || thread.waiting_on_person === true),\n );\n return (\n <>\n {(threads.threads !== null || count > 0) && (\n <div className={classes(classNames, \"sc-space__bar\")}>\n {threads.threads !== null && (\n <button\n type=\"button\"\n part=\"threads-open\"\n className={classes(classNames, \"sc-space__threads\")}\n onClick={() => {\n setBrowsing(true);\n void threads.refresh();\n }}\n >\n {words.threads.open}\n {news && <span aria-hidden className={classes(classNames, \"sc-space__dot\")} />}\n </button>\n )}\n {count > 0 && (\n <button\n type=\"button\"\n part=\"tasks-open\"\n className={classes(classNames, \"sc-space__tasks\")}\n onClick={() => {\n setListing(true);\n void theirs.refresh();\n }}\n >\n {words.tasks.open(count)}\n </button>\n )}\n </div>\n )}\n <AgentChat\n client={client}\n conversationId={id}\n name={name}\n messages={words.chat}\n classNames={classNames}\n components={components}\n locale={locale}\n suggestions={suggestions}\n steps={steps}\n actions={voice ? modes(\"text\") : undefined}\n onTool={onTool}\n onError={onError}\n />\n </>\n );\n };\n\n const close = onClose && (\n <Button\n tone=\"ghost\"\n size=\"sm\"\n icon={XIcon}\n aria-label={words.close}\n title={words.close}\n onClick={onClose}\n part=\"close\"\n className={classes(classNames, \"sc-space__close\")}\n />\n );\n\n return (\n <KitProvider classNames={classNames} messages={words.chat.kit} locale={locale}>\n <section\n ref={root}\n aria-label={name || undefined}\n part=\"space\"\n className={`sc ${classes(classNames, \"sc-space\", ...parts)}`}\n >\n {header ? (\n <header part=\"header\" className={classes(classNames, \"sc-space__face\")}>\n <span\n aria-hidden\n className={classes(classNames, \"sc-space__mark\", `sc-space__mark--${tint}`)}\n >\n <Glyph weight=\"duotone\" />\n </span>\n <div className={classes(classNames, \"sc-space__who\")}>\n <h2 className={classes(classNames, \"sc-space__name\")}>{name}</h2>\n {face?.role && <p className={classes(classNames, \"sc-space__role\")}>{face.role}</p>}\n </div>\n {close}\n </header>\n ) : (\n close\n )}\n <div className={classes(classNames, \"sc-space__body\")}>{body()}</div>\n </section>\n </KitProvider>\n );\n}\n\n/**\n * What is being said on the call, under the orb: the person's last words, and the agent's answer\n * as it comes, read live from the call's own conversation (where the platform keeps them). The\n * host's tools the agent calls meanwhile run here too, as in the chat.\n */\nfunction Captions({\n client,\n conversationId,\n onTool,\n classNames,\n}: {\n client: Client;\n conversationId: string;\n onTool: ToolHandler | undefined;\n classNames: Partial<Record<SpacePart, string>> | undefined;\n}) {\n const live = useConversation(client, conversationId, { onTool });\n const { person, agent } = captionsOf(live);\n return (\n <>\n {person !== \"\" && (\n <p className={classes(classNames, \"sc-space__caption\", \"sc-space__caption--person\")}>\n {person}\n </p>\n )}\n {agent !== \"\" && (\n <p className={classes(classNames, \"sc-space__caption\", \"sc-space__caption--agent\")}>\n {agent}\n </p>\n )}\n </>\n );\n}\n\n/**\n * The last exchange of a call, as its captions say it: what the person last said, and the agent's\n * words after it (as they stream, else the last it said; its greeting before anything was said).\n */\nexport function captionsOf({\n messages,\n pending,\n draft,\n}: Pick<LiveConversation, \"messages\" | \"pending\" | \"draft\">): {\n person: string;\n agent: string;\n} {\n const lastSaid = messages.findLastIndex((message) => message.role === \"user\");\n const replied = messages\n .slice(lastSaid + 1)\n .findLast(\n (message) =>\n message.role === \"assistant\" && message.author !== \"operator\" && message.text !== \"\",\n );\n return {\n person: messages[lastSaid]?.text ?? \"\",\n agent: pending && draft !== \"\" ? draft : (replied?.text ?? \"\"),\n };\n}\n\n/** Whether a conversation asked for is gone for this person: not there, or not theirs. */\nconst isGone = (error: unknown): boolean =>\n error instanceof ApiError && (error.status === 404 || error.status === 403);\n\n/**\n * The conversation the space shows: the one given while it is still the person's, else a new one\n * with the agent, told to `onConversation`. Asked once per attempt (a second effect of the same\n * attempt, as React's strict mode runs, waits on the first rather than starting another); after\n * that, the conversation it opened is the one it goes on with, until it goes on in another (`go`:\n * a call's), told the same way.\n */\nfunction useOpened(\n client: Client,\n agentId: string,\n given: string | undefined,\n onConversation: ((id: string) => void) | undefined,\n): { id: string | null; failed: boolean; retry: () => void; go: (id: string) => void } {\n const kept = useRef(given);\n const asking = useRef<{ client: Client; attempt: number; id: Promise<string> } | null>(null);\n const [id, setId] = useState<string | null>(null);\n const [failed, setFailed] = useState(false);\n const [attempt, setAttempt] = useState(0);\n // The host's ear as it last gave it, told from the effect's answer and from a call's end alike.\n const told = useRef(onConversation);\n useEffect(() => {\n told.current = onConversation;\n });\n const started = (conversation: string) => {\n told.current?.(conversation);\n };\n\n useEffect(() => {\n let current = true;\n const start = () =>\n client.conversations.start(agentId).then((conversation) => {\n started(conversation.id);\n return conversation.id;\n });\n const open = (): Promise<string> => {\n const known = kept.current;\n if (known === undefined) return start();\n return client.conversations.get(known).then(\n (conversation) => conversation.id,\n (error: unknown) => (isGone(error) ? start() : Promise.reject(error)),\n );\n };\n const asked = asking.current;\n const ask =\n asked?.client === client && asked.attempt === attempt\n ? asked\n : { client, attempt, id: open() };\n asking.current = ask;\n ask.id.then(\n (opened) => {\n if (!current) return;\n kept.current = opened;\n setId(opened);\n },\n () => {\n if (current) setFailed(true);\n },\n );\n return () => {\n current = false;\n };\n }, [client, agentId, attempt]);\n\n return {\n id,\n failed,\n retry: () => {\n setFailed(false);\n setAttempt((count) => count + 1);\n },\n go: (conversation) => {\n if (conversation === kept.current) return;\n kept.current = conversation;\n setId(conversation);\n started(conversation);\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/AgentTasks.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { classes } from "./classes.js";
|
|
2
2
|
import { Automation } from "./automation/Automation.js";
|
|
3
3
|
import { Button } from "./kit/Button.js";
|
|
4
|
+
import { taskCard } from "./automation/types.js";
|
|
4
5
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
5
6
|
import { ArrowLeftIcon, TrashIcon } from "@phosphor-icons/react";
|
|
6
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -96,7 +97,7 @@ function AgentTasks({ client, agentId, tasks, onChange, onBack, words, className
|
|
|
96
97
|
}) : /* @__PURE__ */ jsx("div", {
|
|
97
98
|
className: classes(classNames, "sc-tasks__list"),
|
|
98
99
|
children: tasks.map((task) => /* @__PURE__ */ jsx(Automation, {
|
|
99
|
-
automation: task,
|
|
100
|
+
automation: taskCard(task),
|
|
100
101
|
runs: task.runs,
|
|
101
102
|
switching: busy === task.id,
|
|
102
103
|
onSwitch: (on) => void change(task, () => client.tasks.switch(agentId, task.id, on)),
|
package/dist/AgentTasks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentTasks.js","names":[],"sources":["../src/AgentTasks.tsx"],"sourcesContent":["import { ArrowLeftIcon, TrashIcon } from \"@phosphor-icons/react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { Automation } from \"./automation/Automation\";\nimport type
|
|
1
|
+
{"version":3,"file":"AgentTasks.js","names":[],"sources":["../src/AgentTasks.tsx"],"sourcesContent":["import { ArrowLeftIcon, TrashIcon } from \"@phosphor-icons/react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { Automation } from \"./automation/Automation\";\nimport { taskCard, type Task } from \"./automation/types\";\nimport { classes } from \"./classes\";\nimport type { Client } from \"./client\";\nimport { Button } from \"./kit/Button\";\n\n/** What the person's tasks say, in the space; a host gives its own, in its language. */\nexport interface TaskMessages {\n /** Over the chat, when the agent runs any for them: how many, to open them. */\n open: (count: number) => string;\n /** Over the list. */\n title: string;\n back: string;\n /** Taking one away. */\n remove: string;\n /** None left. */\n empty: string;\n /** A change that didn't go through. */\n failed: string;\n}\n\nexport const TASK_MESSAGES: TaskMessages = {\n open: (count) => (count === 1 ? \"1 task set up for you\" : `${count} tasks set up for you`),\n title: \"Your tasks\",\n back: \"Back to the conversation\",\n remove: \"Remove\",\n empty: \"Nothing set up. Ask the agent to remind you of something, or to check back later.\",\n failed: \"That didn't go through. Try again.\",\n};\n\nexport type TaskPart =\n | \"sc-tasks\"\n | \"sc-tasks__head\"\n | \"sc-tasks__title\"\n | \"sc-tasks__list\"\n | \"sc-tasks__empty\"\n | \"sc-tasks__problem\";\n\n/** How often the person's tasks are read again while the space is open. */\nexport const TASKS_POLL_MS = 20_000;\n\n/**\n * The tasks the agent runs for the person the client speaks for, read when the space opens,\n * again every `TASKS_POLL_MS` and on `refresh`. Null until read, and whenever it can't be (an API\n * without tasks, the business's own client): the space then shows none.\n */\nexport function useTasks(\n client: Client,\n agentId: string,\n enabled: boolean,\n): { tasks: Task[] | null; refresh: () => Promise<void> } {\n const [tasks, setTasks] = useState<Task[] | null>(null);\n const live = useRef(true);\n const refresh = useCallback(async () => {\n try {\n const read = await client.tasks.list(agentId);\n if (live.current) setTasks(read);\n } catch {\n if (live.current) setTasks(null);\n }\n }, [client, agentId]);\n useEffect(() => {\n live.current = true;\n if (!enabled) return;\n void refresh();\n const timer = setInterval(() => void refresh(), TASKS_POLL_MS);\n return () => {\n live.current = false;\n clearInterval(timer);\n };\n }, [enabled, refresh]);\n return { tasks: enabled ? tasks : null, refresh };\n}\n\nexport interface AgentTasksProps {\n client: Client;\n agentId: string;\n tasks: readonly Task[];\n /** Read them again, after a change. */\n onChange: () => Promise<void>;\n onBack: () => void;\n words: TaskMessages;\n classNames?: Partial<Record<TaskPart, string>> | undefined;\n}\n\n/**\n * The person's tasks, as they asked the agent for them: each as an automation reads (when, its\n * words, its last runs and how each went), with their switch to pause it and the way to take it\n * away; the way back to the conversation over them.\n */\nexport function AgentTasks({\n client,\n agentId,\n tasks,\n onChange,\n onBack,\n words,\n classNames,\n}: AgentTasksProps) {\n const [busy, setBusy] = useState<string | null>(null);\n const [failed, setFailed] = useState(false);\n const change = async (task: Task, doing: () => Promise<unknown>) => {\n setBusy(task.id);\n setFailed(false);\n try {\n await doing();\n await onChange();\n } catch {\n setFailed(true);\n } finally {\n setBusy(null);\n }\n };\n return (\n <section aria-label={words.title} part=\"tasks\" className={classes(classNames, \"sc-tasks\")}>\n <header className={classes(classNames, \"sc-tasks__head\")}>\n <Button\n tone=\"ghost\"\n size=\"sm\"\n icon={ArrowLeftIcon}\n aria-label={words.back}\n title={words.back}\n onClick={onBack}\n />\n <h3 className={classes(classNames, \"sc-tasks__title\")}>{words.title}</h3>\n </header>\n {failed && (\n <p role=\"alert\" className={classes(classNames, \"sc-tasks__problem\")}>\n {words.failed}\n </p>\n )}\n {tasks.length === 0 ? (\n <p className={classes(classNames, \"sc-tasks__empty\")}>{words.empty}</p>\n ) : (\n <div className={classes(classNames, \"sc-tasks__list\")}>\n {tasks.map((task) => (\n <Automation\n key={task.id}\n automation={taskCard(task)}\n runs={task.runs}\n switching={busy === task.id}\n onSwitch={(on) => void change(task, () => client.tasks.switch(agentId, task.id, on))}\n actions={\n <Button\n tone=\"ghost\"\n size=\"sm\"\n icon={TrashIcon}\n aria-label={words.remove}\n title={words.remove}\n disabled={busy === task.id}\n onClick={() => void change(task, () => client.tasks.remove(agentId, task.id))}\n />\n }\n />\n ))}\n </div>\n )}\n </section>\n );\n}\n"],"mappings":";;;;;;;;AAwBA,MAAa,gBAA8B;CACzC,OAAO,UAAW,UAAU,IAAI,0BAA0B,GAAG,MAAM;CACnE,OAAO;CACP,MAAM;CACN,QAAQ;CACR,OAAO;CACP,QAAQ;AACV;;AAWA,MAAa,gBAAgB;;;;;;AAO7B,SAAgB,SACd,QACA,SACA,SACwD;CACxD,MAAM,CAAC,OAAO,YAAY,SAAwB,IAAI;CACtD,MAAM,OAAO,OAAO,IAAI;CACxB,MAAM,UAAU,YAAY,YAAY;EACtC,IAAI;GACF,MAAM,OAAO,MAAM,OAAO,MAAM,KAAK,OAAO;GAC5C,IAAI,KAAK,SAAS,SAAS,IAAI;EACjC,QAAQ;GACN,IAAI,KAAK,SAAS,SAAS,IAAI;EACjC;CACF,GAAG,CAAC,QAAQ,OAAO,CAAC;CACpB,gBAAgB;EACd,KAAK,UAAU;EACf,IAAI,CAAC,SAAS;EACd,QAAa;EACb,MAAM,QAAQ,kBAAkB,KAAK,QAAQ,GAAG,aAAa;EAC7D,aAAa;GACX,KAAK,UAAU;GACf,cAAc,KAAK;EACrB;CACF,GAAG,CAAC,SAAS,OAAO,CAAC;CACrB,OAAO;EAAE,OAAO,UAAU,QAAQ;EAAM;CAAQ;AAClD;;;;;;AAkBA,SAAgB,WAAW,EACzB,QACA,SACA,OACA,UACA,QACA,OACA,cACkB;CAClB,MAAM,CAAC,MAAM,WAAW,SAAwB,IAAI;CACpD,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAC1C,MAAM,SAAS,OAAO,MAAY,UAAkC;EAClE,QAAQ,KAAK,EAAE;EACf,UAAU,KAAK;EACf,IAAI;GACF,MAAM,MAAM;GACZ,MAAM,SAAS;EACjB,QAAQ;GACN,UAAU,IAAI;EAChB,UAAU;GACR,QAAQ,IAAI;EACd;CACF;CACA,OACE,qBAAC,WAAD;EAAS,cAAY,MAAM;EAAO,MAAK;EAAQ,WAAW,QAAQ,YAAY,UAAU;EAAxF,UAAA;GACE,qBAAC,UAAD;IAAQ,WAAW,QAAQ,YAAY,gBAAgB;IAAvD,UAAA,CACE,oBAAC,QAAD;KACE,MAAK;KACL,MAAK;KACL,MAAM;KACN,cAAY,MAAM;KAClB,OAAO,MAAM;KACb,SAAS;IACV,CAAA,GACD,oBAAC,MAAD;KAAI,WAAW,QAAQ,YAAY,iBAAiB;KAAI,UAAA,MAAM;IAAU,CAAA,CAClE;;GACP,UACC,oBAAC,KAAD;IAAG,MAAK;IAAQ,WAAW,QAAQ,YAAY,mBAAmB;IAC/D,UAAA,MAAM;GACN,CAAA;GAEJ,MAAM,WAAW,IAChB,oBAAC,KAAD;IAAG,WAAW,QAAQ,YAAY,iBAAiB;IAAI,UAAA,MAAM;GAAS,CAAA,IAEtE,oBAAC,OAAD;IAAK,WAAW,QAAQ,YAAY,gBAAgB;IACjD,UAAA,MAAM,KAAK,SACV,oBAAC,YAAD;KAEE,YAAY,SAAS,IAAI;KACzB,MAAM,KAAK;KACX,WAAW,SAAS,KAAK;KACzB,WAAW,OAAO,KAAK,OAAO,YAAY,OAAO,MAAM,OAAO,SAAS,KAAK,IAAI,EAAE,CAAC;KACnF,SACE,oBAAC,QAAD;MACE,MAAK;MACL,MAAK;MACL,MAAM;MACN,cAAY,MAAM;MAClB,OAAO,MAAM;MACb,UAAU,SAAS,KAAK;MACxB,eAAe,KAAK,OAAO,YAAY,OAAO,MAAM,OAAO,SAAS,KAAK,EAAE,CAAC;KAC7E,CAAA;IAEJ,GAhBM,KAAK,EAgBX,CACF;GACE,CAAA;EAEA;;AAEb"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Conversation as Conversation$1 } from "./types.js";
|
|
2
|
+
import { Client } from "./client.js";
|
|
3
|
+
import { AgentChatProps } from "./AgentChat.js";
|
|
4
|
+
//#region src/Threads.d.ts
|
|
5
|
+
/** What the person's threads say; a host gives its own, in its language. */
|
|
6
|
+
export interface ThreadMessages {
|
|
7
|
+
/** Over the chat, to open the list; and the list's title. */
|
|
8
|
+
open: string;
|
|
9
|
+
title: string;
|
|
10
|
+
back: string;
|
|
11
|
+
/** Starts another conversation with the agent. */
|
|
12
|
+
new: string;
|
|
13
|
+
/** A thread nothing names yet. */
|
|
14
|
+
untitled: string;
|
|
15
|
+
/** Beside a thread: how many of the agent's messages are unread; that it waits on them. */
|
|
16
|
+
unread: (count: number) => string;
|
|
17
|
+
waiting: string;
|
|
18
|
+
empty: string;
|
|
19
|
+
}
|
|
20
|
+
export declare const THREAD_MESSAGES: ThreadMessages;
|
|
21
|
+
export type ThreadPart = "sc-threads" | "sc-threads__head" | "sc-threads__title" | "sc-threads__list" | "sc-threads__item" | "sc-threads__item--current" | "sc-threads__name" | "sc-threads__badge" | "sc-threads__dot" | "sc-threads__empty";
|
|
22
|
+
/** How often the person's threads are read again, for what is unread. */
|
|
23
|
+
export declare const THREADS_POLL_MS = 20000;
|
|
24
|
+
/** The person's threads with an agent, and what of them waits on the person. */
|
|
25
|
+
export interface Unread {
|
|
26
|
+
/** Newest first; null until read, and whenever they can't be. */
|
|
27
|
+
threads: Conversation$1[] | null;
|
|
28
|
+
/** Every unread message of the agent's, across the threads. */
|
|
29
|
+
unread: number;
|
|
30
|
+
/** Whether a thread waits on the person (a question, their yes). */
|
|
31
|
+
waiting: boolean;
|
|
32
|
+
refresh: () => Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The threads the person the client speaks for has with the agent, read now, again every
|
|
36
|
+
* `THREADS_POLL_MS`, when the page comes back into sight, and on `refresh`: what draws a dot on
|
|
37
|
+
* a launcher, a count beside a tab. `enabled` off reads nothing.
|
|
38
|
+
*/
|
|
39
|
+
export declare function useUnread(client: Client, agentId: string, enabled?: boolean): Unread;
|
|
40
|
+
export interface ThreadsProps {
|
|
41
|
+
/** The threads, newest first (`useUnread`'s). */
|
|
42
|
+
threads: readonly Conversation$1[];
|
|
43
|
+
/** The one open now, marked. */
|
|
44
|
+
current?: string | null | undefined;
|
|
45
|
+
onSelect: (id: string) => void;
|
|
46
|
+
/** Starts another; without it, no button for it. */
|
|
47
|
+
onNew?: (() => void) | undefined;
|
|
48
|
+
/** The way back to the open conversation; without it, none. */
|
|
49
|
+
onBack?: (() => void) | undefined;
|
|
50
|
+
messages?: Partial<ThreadMessages> | undefined;
|
|
51
|
+
classNames?: Partial<Record<ThreadPart, string>> | undefined;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The person's threads with the agent: each by its title, how many of the agent's messages in
|
|
55
|
+
* it are unread, a dot when it waits on them; the open one marked; another to start.
|
|
56
|
+
*/
|
|
57
|
+
export declare function Threads({ threads, current, onSelect, onNew, onBack, messages, classNames }: ThreadsProps): import("react").JSX.Element;
|
|
58
|
+
/**
|
|
59
|
+
* A conversation between a person and one agent, as the API reads it (`types.ts`): one name for
|
|
60
|
+
* the shape and for the component that shows one.
|
|
61
|
+
*/
|
|
62
|
+
export type Conversation = Conversation$1;
|
|
63
|
+
export type ConversationProps = Omit<AgentChatProps, "conversationId"> & {
|
|
64
|
+
/** The conversation to show, by its id. */
|
|
65
|
+
id: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* One conversation, for a host that lays out its own threads: the chat (`AgentChat`), which reads
|
|
69
|
+
* what comes as it shows it on a person's page, so their `unread` follows.
|
|
70
|
+
*/
|
|
71
|
+
export declare function Conversation({ id, ...chat }: ConversationProps): import("react").JSX.Element;
|
|
72
|
+
//#endregion
|
|
73
|
+
//# sourceMappingURL=Threads.d.ts.map
|
package/dist/Threads.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { classes } from "./classes.js";
|
|
2
|
+
import { Button } from "./kit/Button.js";
|
|
3
|
+
import { AgentChat } from "./AgentChat.js";
|
|
4
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
5
|
+
import { ArrowLeftIcon, PlusIcon } from "@phosphor-icons/react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/Threads.tsx
|
|
8
|
+
const THREAD_MESSAGES = {
|
|
9
|
+
open: "Conversations",
|
|
10
|
+
title: "Your conversations",
|
|
11
|
+
back: "Back to the conversation",
|
|
12
|
+
new: "New conversation",
|
|
13
|
+
untitled: "New conversation",
|
|
14
|
+
unread: (count) => count === 1 ? "1 unread message" : `${count} unread messages`,
|
|
15
|
+
waiting: "Waiting for you",
|
|
16
|
+
empty: "Nothing here yet."
|
|
17
|
+
};
|
|
18
|
+
/** How often the person's threads are read again, for what is unread. */
|
|
19
|
+
const THREADS_POLL_MS = 2e4;
|
|
20
|
+
/**
|
|
21
|
+
* The threads the person the client speaks for has with the agent, read now, again every
|
|
22
|
+
* `THREADS_POLL_MS`, when the page comes back into sight, and on `refresh`: what draws a dot on
|
|
23
|
+
* a launcher, a count beside a tab. `enabled` off reads nothing.
|
|
24
|
+
*/
|
|
25
|
+
function useUnread(client, agentId, enabled = true) {
|
|
26
|
+
const [threads, setThreads] = useState(null);
|
|
27
|
+
const live = useRef(true);
|
|
28
|
+
const refresh = useCallback(async () => {
|
|
29
|
+
try {
|
|
30
|
+
const read = await client.conversations.list(agentId);
|
|
31
|
+
if (live.current) setThreads(read);
|
|
32
|
+
} catch {
|
|
33
|
+
if (live.current) setThreads(null);
|
|
34
|
+
}
|
|
35
|
+
}, [client, agentId]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
live.current = true;
|
|
38
|
+
if (!enabled) return void 0;
|
|
39
|
+
refresh();
|
|
40
|
+
const timer = setInterval(() => void refresh(), THREADS_POLL_MS);
|
|
41
|
+
const shown = () => {
|
|
42
|
+
if (document.visibilityState === "visible") refresh();
|
|
43
|
+
};
|
|
44
|
+
document.addEventListener("visibilitychange", shown);
|
|
45
|
+
return () => {
|
|
46
|
+
live.current = false;
|
|
47
|
+
clearInterval(timer);
|
|
48
|
+
document.removeEventListener("visibilitychange", shown);
|
|
49
|
+
};
|
|
50
|
+
}, [enabled, refresh]);
|
|
51
|
+
const read = enabled ? threads : null;
|
|
52
|
+
return {
|
|
53
|
+
threads: read,
|
|
54
|
+
unread: (read ?? []).reduce((sum, thread) => sum + (thread.unread ?? 0), 0),
|
|
55
|
+
waiting: (read ?? []).some((thread) => thread.waiting_on_person === true),
|
|
56
|
+
refresh
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The person's threads with the agent: each by its title, how many of the agent's messages in
|
|
61
|
+
* it are unread, a dot when it waits on them; the open one marked; another to start.
|
|
62
|
+
*/
|
|
63
|
+
function Threads({ threads, current = null, onSelect, onNew, onBack, messages, classNames }) {
|
|
64
|
+
const words = {
|
|
65
|
+
...THREAD_MESSAGES,
|
|
66
|
+
...messages
|
|
67
|
+
};
|
|
68
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
69
|
+
"aria-label": words.title,
|
|
70
|
+
part: "threads",
|
|
71
|
+
className: classes(classNames, "sc-threads"),
|
|
72
|
+
children: [/* @__PURE__ */ jsxs("header", {
|
|
73
|
+
className: classes(classNames, "sc-threads__head"),
|
|
74
|
+
children: [
|
|
75
|
+
onBack && /* @__PURE__ */ jsx(Button, {
|
|
76
|
+
tone: "ghost",
|
|
77
|
+
size: "sm",
|
|
78
|
+
icon: ArrowLeftIcon,
|
|
79
|
+
"aria-label": words.back,
|
|
80
|
+
title: words.back,
|
|
81
|
+
onClick: onBack
|
|
82
|
+
}),
|
|
83
|
+
/* @__PURE__ */ jsx("h3", {
|
|
84
|
+
className: classes(classNames, "sc-threads__title"),
|
|
85
|
+
children: words.title
|
|
86
|
+
}),
|
|
87
|
+
onNew && /* @__PURE__ */ jsx(Button, {
|
|
88
|
+
tone: "ghost",
|
|
89
|
+
size: "sm",
|
|
90
|
+
icon: PlusIcon,
|
|
91
|
+
onClick: onNew,
|
|
92
|
+
children: words.new
|
|
93
|
+
})
|
|
94
|
+
]
|
|
95
|
+
}), threads.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
96
|
+
className: classes(classNames, "sc-threads__empty"),
|
|
97
|
+
children: words.empty
|
|
98
|
+
}) : /* @__PURE__ */ jsx("ul", {
|
|
99
|
+
className: classes(classNames, "sc-threads__list"),
|
|
100
|
+
children: threads.map((thread) => {
|
|
101
|
+
const unread = thread.unread ?? 0;
|
|
102
|
+
const open = thread.id === current;
|
|
103
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", {
|
|
104
|
+
type: "button",
|
|
105
|
+
"aria-current": open || void 0,
|
|
106
|
+
className: classes(classNames, "sc-threads__item", ...open ? ["sc-threads__item--current"] : []),
|
|
107
|
+
onClick: () => {
|
|
108
|
+
onSelect(thread.id);
|
|
109
|
+
},
|
|
110
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
111
|
+
className: classes(classNames, "sc-threads__name"),
|
|
112
|
+
children: thread.title ?? words.untitled
|
|
113
|
+
}), unread > 0 ? /* @__PURE__ */ jsx("span", {
|
|
114
|
+
"aria-label": words.unread(unread),
|
|
115
|
+
className: classes(classNames, "sc-threads__badge"),
|
|
116
|
+
children: unread
|
|
117
|
+
}) : thread.waiting_on_person === true && /* @__PURE__ */ jsx("span", {
|
|
118
|
+
role: "img",
|
|
119
|
+
"aria-label": words.waiting,
|
|
120
|
+
className: classes(classNames, "sc-threads__dot")
|
|
121
|
+
})]
|
|
122
|
+
}) }, thread.id);
|
|
123
|
+
})
|
|
124
|
+
})]
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* One conversation, for a host that lays out its own threads: the chat (`AgentChat`), which reads
|
|
129
|
+
* what comes as it shows it on a person's page, so their `unread` follows.
|
|
130
|
+
*/
|
|
131
|
+
function Conversation({ id, ...chat }) {
|
|
132
|
+
return /* @__PURE__ */ jsx(AgentChat, {
|
|
133
|
+
conversationId: id,
|
|
134
|
+
...chat
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
export { Conversation, THREADS_POLL_MS, THREAD_MESSAGES, Threads, useUnread };
|
|
139
|
+
|
|
140
|
+
//# sourceMappingURL=Threads.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Threads.js","names":[],"sources":["../src/Threads.tsx"],"sourcesContent":["import { ArrowLeftIcon, PlusIcon } from \"@phosphor-icons/react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { AgentChat, type AgentChatProps } from \"./AgentChat\";\nimport { classes } from \"./classes\";\nimport type { Client } from \"./client\";\nimport { Button } from \"./kit/Button\";\nimport type { Conversation as Thread } from \"./types\";\n\n/** What the person's threads say; a host gives its own, in its language. */\nexport interface ThreadMessages {\n /** Over the chat, to open the list; and the list's title. */\n open: string;\n title: string;\n back: string;\n /** Starts another conversation with the agent. */\n new: string;\n /** A thread nothing names yet. */\n untitled: string;\n /** Beside a thread: how many of the agent's messages are unread; that it waits on them. */\n unread: (count: number) => string;\n waiting: string;\n empty: string;\n}\n\nexport const THREAD_MESSAGES: ThreadMessages = {\n open: \"Conversations\",\n title: \"Your conversations\",\n back: \"Back to the conversation\",\n new: \"New conversation\",\n untitled: \"New conversation\",\n unread: (count) => (count === 1 ? \"1 unread message\" : `${count} unread messages`),\n waiting: \"Waiting for you\",\n empty: \"Nothing here yet.\",\n};\n\nexport type ThreadPart =\n | \"sc-threads\"\n | \"sc-threads__head\"\n | \"sc-threads__title\"\n | \"sc-threads__list\"\n | \"sc-threads__item\"\n | \"sc-threads__item--current\"\n | \"sc-threads__name\"\n | \"sc-threads__badge\"\n | \"sc-threads__dot\"\n | \"sc-threads__empty\";\n\n/** How often the person's threads are read again, for what is unread. */\nexport const THREADS_POLL_MS = 20_000;\n\n/** The person's threads with an agent, and what of them waits on the person. */\nexport interface Unread {\n /** Newest first; null until read, and whenever they can't be. */\n threads: Thread[] | null;\n /** Every unread message of the agent's, across the threads. */\n unread: number;\n /** Whether a thread waits on the person (a question, their yes). */\n waiting: boolean;\n refresh: () => Promise<void>;\n}\n\n/**\n * The threads the person the client speaks for has with the agent, read now, again every\n * `THREADS_POLL_MS`, when the page comes back into sight, and on `refresh`: what draws a dot on\n * a launcher, a count beside a tab. `enabled` off reads nothing.\n */\nexport function useUnread(client: Client, agentId: string, enabled = true): Unread {\n const [threads, setThreads] = useState<Thread[] | null>(null);\n const live = useRef(true);\n const refresh = useCallback(async () => {\n try {\n const read = await client.conversations.list(agentId);\n if (live.current) setThreads(read);\n } catch {\n if (live.current) setThreads(null);\n }\n }, [client, agentId]);\n useEffect(() => {\n live.current = true;\n if (!enabled) return undefined;\n void refresh();\n const timer = setInterval(() => void refresh(), THREADS_POLL_MS);\n const shown = () => {\n if (document.visibilityState === \"visible\") void refresh();\n };\n document.addEventListener(\"visibilitychange\", shown);\n return () => {\n live.current = false;\n clearInterval(timer);\n document.removeEventListener(\"visibilitychange\", shown);\n };\n }, [enabled, refresh]);\n const read = enabled ? threads : null;\n return {\n threads: read,\n unread: (read ?? []).reduce((sum, thread) => sum + (thread.unread ?? 0), 0),\n waiting: (read ?? []).some((thread) => thread.waiting_on_person === true),\n refresh,\n };\n}\n\nexport interface ThreadsProps {\n /** The threads, newest first (`useUnread`'s). */\n threads: readonly Thread[];\n /** The one open now, marked. */\n current?: string | null | undefined;\n onSelect: (id: string) => void;\n /** Starts another; without it, no button for it. */\n onNew?: (() => void) | undefined;\n /** The way back to the open conversation; without it, none. */\n onBack?: (() => void) | undefined;\n messages?: Partial<ThreadMessages> | undefined;\n classNames?: Partial<Record<ThreadPart, string>> | undefined;\n}\n\n/**\n * The person's threads with the agent: each by its title, how many of the agent's messages in\n * it are unread, a dot when it waits on them; the open one marked; another to start.\n */\nexport function Threads({\n threads,\n current = null,\n onSelect,\n onNew,\n onBack,\n messages,\n classNames,\n}: ThreadsProps) {\n const words = { ...THREAD_MESSAGES, ...messages };\n return (\n <section aria-label={words.title} part=\"threads\" className={classes(classNames, \"sc-threads\")}>\n <header className={classes(classNames, \"sc-threads__head\")}>\n {onBack && (\n <Button\n tone=\"ghost\"\n size=\"sm\"\n icon={ArrowLeftIcon}\n aria-label={words.back}\n title={words.back}\n onClick={onBack}\n />\n )}\n <h3 className={classes(classNames, \"sc-threads__title\")}>{words.title}</h3>\n {onNew && (\n <Button tone=\"ghost\" size=\"sm\" icon={PlusIcon} onClick={onNew}>\n {words.new}\n </Button>\n )}\n </header>\n {threads.length === 0 ? (\n <p className={classes(classNames, \"sc-threads__empty\")}>{words.empty}</p>\n ) : (\n <ul className={classes(classNames, \"sc-threads__list\")}>\n {threads.map((thread) => {\n const unread = thread.unread ?? 0;\n const open = thread.id === current;\n return (\n <li key={thread.id}>\n <button\n type=\"button\"\n aria-current={open || undefined}\n className={classes(\n classNames,\n \"sc-threads__item\",\n ...(open ? ([\"sc-threads__item--current\"] as const) : []),\n )}\n onClick={() => {\n onSelect(thread.id);\n }}\n >\n <span className={classes(classNames, \"sc-threads__name\")}>\n {thread.title ?? words.untitled}\n </span>\n {unread > 0 ? (\n <span\n aria-label={words.unread(unread)}\n className={classes(classNames, \"sc-threads__badge\")}\n >\n {unread}\n </span>\n ) : (\n thread.waiting_on_person === true && (\n <span\n role=\"img\"\n aria-label={words.waiting}\n className={classes(classNames, \"sc-threads__dot\")}\n />\n )\n )}\n </button>\n </li>\n );\n })}\n </ul>\n )}\n </section>\n );\n}\n\n/**\n * A conversation between a person and one agent, as the API reads it (`types.ts`): one name for\n * the shape and for the component that shows one.\n */\nexport type Conversation = Thread;\n\nexport type ConversationProps = Omit<AgentChatProps, \"conversationId\"> & {\n /** The conversation to show, by its id. */\n id: string;\n};\n\n/**\n * One conversation, for a host that lays out its own threads: the chat (`AgentChat`), which reads\n * what comes as it shows it on a person's page, so their `unread` follows.\n */\nexport function Conversation({ id, ...chat }: ConversationProps) {\n return <AgentChat conversationId={id} {...chat} />;\n}\n"],"mappings":"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"}
|
package/dist/VoiceCall.js
CHANGED
|
@@ -52,7 +52,7 @@ const VOICE_CALL_MESSAGES = {
|
|
|
52
52
|
},
|
|
53
53
|
builder: {
|
|
54
54
|
title: (name) => `${name} doesn't take calls`,
|
|
55
|
-
help: "
|
|
55
|
+
help: "The builder only writes. Call one of the agents."
|
|
56
56
|
},
|
|
57
57
|
refused: {
|
|
58
58
|
title: (name) => `${name} can't take this call`,
|