@webless/agent 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-HXKD46I6.js → chunk-U2TIUZJM.js} +2540 -666
- package/dist/chunk-U2TIUZJM.js.map +1 -0
- package/dist/embed.cjs +2276 -407
- package/dist/embed.cjs.map +1 -1
- package/dist/embed.css +749 -112
- package/dist/embed.css.map +1 -1
- package/dist/embed.d.cts +9 -3
- package/dist/embed.d.ts +9 -3
- package/dist/embed.js +6 -2
- package/dist/embed.js.map +1 -1
- package/dist/index.cjs +431 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +102 -1
- package/dist/index.d.ts +102 -1
- package/dist/index.js +428 -23
- package/dist/index.js.map +1 -1
- package/dist/manifest-Cad-TQJw.d.cts +391 -0
- package/dist/manifest-Cad-TQJw.d.ts +391 -0
- package/dist/react.cjs +2263 -406
- package/dist/react.cjs.map +1 -1
- package/dist/react.css +749 -112
- package/dist/react.css.map +1 -1
- package/dist/react.d.cts +13 -7
- package/dist/react.d.ts +13 -7
- package/dist/react.js +5 -1
- package/package.json +1 -1
- package/dist/chunk-HXKD46I6.js.map +0 -1
- package/dist/manifest-H6oHn8Pc.d.cts +0 -185
- package/dist/manifest-H6oHn8Pc.d.ts +0 -185
|
@@ -1,3 +1,993 @@
|
|
|
1
|
+
// src/runtime/tool-ui.ts
|
|
2
|
+
var AGENT_TOOL_UI_SCHEMA_VERSION = "webless.tool-ui.v1";
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
function isJsonValue(value, seen = /* @__PURE__ */ new Set()) {
|
|
7
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
11
|
+
if (typeof value !== "object") return false;
|
|
12
|
+
if (seen.has(value)) return false;
|
|
13
|
+
seen.add(value);
|
|
14
|
+
const valid = Array.isArray(value) ? value.every((item) => isJsonValue(item, seen)) : Object.entries(value).every(
|
|
15
|
+
([key, item]) => typeof key === "string" && isJsonValue(item, seen)
|
|
16
|
+
);
|
|
17
|
+
seen.delete(value);
|
|
18
|
+
return valid;
|
|
19
|
+
}
|
|
20
|
+
function boundedString(value, max) {
|
|
21
|
+
if (typeof value !== "string" || value.trim().length === 0 || value.length > max) {
|
|
22
|
+
return void 0;
|
|
23
|
+
}
|
|
24
|
+
return value.trim();
|
|
25
|
+
}
|
|
26
|
+
function numberValue(value) {
|
|
27
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
28
|
+
}
|
|
29
|
+
function integerValue(value) {
|
|
30
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 8e3 ? value : void 0;
|
|
31
|
+
}
|
|
32
|
+
function isFieldKind(value) {
|
|
33
|
+
return typeof value === "string" && [
|
|
34
|
+
"text",
|
|
35
|
+
"textarea",
|
|
36
|
+
"email",
|
|
37
|
+
"number",
|
|
38
|
+
"select",
|
|
39
|
+
"multi-select",
|
|
40
|
+
"checkbox",
|
|
41
|
+
"confirmation",
|
|
42
|
+
"radio",
|
|
43
|
+
"date",
|
|
44
|
+
"time",
|
|
45
|
+
"date-time",
|
|
46
|
+
"calendar",
|
|
47
|
+
"range",
|
|
48
|
+
"json"
|
|
49
|
+
].includes(value);
|
|
50
|
+
}
|
|
51
|
+
function parseField(value) {
|
|
52
|
+
if (!isRecord(value)) return null;
|
|
53
|
+
if (!hasOnlyKeys(value, [
|
|
54
|
+
"description",
|
|
55
|
+
"kind",
|
|
56
|
+
"label",
|
|
57
|
+
"max",
|
|
58
|
+
"maxItems",
|
|
59
|
+
"maxLength",
|
|
60
|
+
"min",
|
|
61
|
+
"minLength",
|
|
62
|
+
"options",
|
|
63
|
+
"path",
|
|
64
|
+
"placeholder",
|
|
65
|
+
"required",
|
|
66
|
+
"step",
|
|
67
|
+
"defaultValue"
|
|
68
|
+
])) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (!isFieldKind(value.kind)) return null;
|
|
72
|
+
const path = boundedString(value.path, 160);
|
|
73
|
+
const label = boundedString(value.label, 160);
|
|
74
|
+
const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
|
|
75
|
+
const placeholder = value.placeholder === void 0 || typeof value.placeholder !== "string" ? void 0 : value.placeholder;
|
|
76
|
+
const required = value.required === void 0 || typeof value.required !== "boolean" ? void 0 : value.required;
|
|
77
|
+
const min = value.min === void 0 ? void 0 : numberValue(value.min);
|
|
78
|
+
const max = value.max === void 0 ? void 0 : numberValue(value.max);
|
|
79
|
+
const maxItems = value.maxItems === void 0 ? void 0 : integerValue(value.maxItems);
|
|
80
|
+
const step = value.step === void 0 ? void 0 : numberValue(value.step);
|
|
81
|
+
const minLength = value.minLength === void 0 ? void 0 : integerValue(value.minLength);
|
|
82
|
+
const maxLength = value.maxLength === void 0 ? void 0 : integerValue(value.maxLength);
|
|
83
|
+
const defaultValue = value.defaultValue === void 0 || !isJsonValue(value.defaultValue) ? void 0 : value.defaultValue;
|
|
84
|
+
if (!path || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || !label) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
if (value.description !== void 0 && !description) return null;
|
|
88
|
+
if (value.placeholder !== void 0 && placeholder === void 0) return null;
|
|
89
|
+
if (value.required !== void 0 && required === void 0) return null;
|
|
90
|
+
if (value.min !== void 0 && min === void 0) return null;
|
|
91
|
+
if (value.max !== void 0 && max === void 0) return null;
|
|
92
|
+
if (value.maxItems !== void 0 && (maxItems === void 0 || maxItems < 1 || maxItems > 100))
|
|
93
|
+
return null;
|
|
94
|
+
if (value.step !== void 0 && step === void 0) return null;
|
|
95
|
+
if (value.minLength !== void 0 && minLength === void 0) return null;
|
|
96
|
+
if (value.maxLength !== void 0 && maxLength === void 0) return null;
|
|
97
|
+
if (value.defaultValue !== void 0 && defaultValue === void 0)
|
|
98
|
+
return null;
|
|
99
|
+
if (value.options !== void 0) {
|
|
100
|
+
if (!Array.isArray(value.options) || value.options.length > 100)
|
|
101
|
+
return null;
|
|
102
|
+
for (const option of value.options) {
|
|
103
|
+
if (!isRecord(option) || !boundedString(option.label, 160) || !isJsonValue(option.value)) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
kind: value.kind,
|
|
110
|
+
path,
|
|
111
|
+
label,
|
|
112
|
+
...description ? { description } : {},
|
|
113
|
+
...placeholder !== void 0 ? { placeholder } : {},
|
|
114
|
+
...required !== void 0 ? { required } : {},
|
|
115
|
+
...defaultValue !== void 0 ? { defaultValue } : {},
|
|
116
|
+
...value.options !== void 0 ? { options: value.options } : {},
|
|
117
|
+
...min !== void 0 ? { min } : {},
|
|
118
|
+
...max !== void 0 ? { max } : {},
|
|
119
|
+
...maxItems !== void 0 ? { maxItems } : {},
|
|
120
|
+
...step !== void 0 ? { step } : {},
|
|
121
|
+
...minLength !== void 0 ? { minLength } : {},
|
|
122
|
+
...maxLength !== void 0 ? { maxLength } : {}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function parseStep(value) {
|
|
126
|
+
if (!isRecord(value)) return null;
|
|
127
|
+
if (!hasOnlyKeys(value, ["description", "fieldPaths", "id", "label"])) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const id = boundedString(value.id, 80);
|
|
131
|
+
const label = boundedString(value.label, 160);
|
|
132
|
+
const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
|
|
133
|
+
if (!id || !/^[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(id) || !label || value.description !== void 0 && !description || !Array.isArray(value.fieldPaths) || value.fieldPaths.length < 1 || value.fieldPaths.length > 32 || value.fieldPaths.some(
|
|
134
|
+
(path) => typeof path !== "string" || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || path.length > 160
|
|
135
|
+
)) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
id,
|
|
140
|
+
label,
|
|
141
|
+
fieldPaths: value.fieldPaths,
|
|
142
|
+
...description ? { description } : {}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function parseAction(value) {
|
|
146
|
+
if (!isRecord(value)) return null;
|
|
147
|
+
if (!hasOnlyKeys(value, ["id", "label"])) return null;
|
|
148
|
+
const label = boundedString(value.label, 80);
|
|
149
|
+
if (value.id !== "submit" && value.id !== "back" && value.id !== "next" && value.id !== "reset" || !label) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
id: value.id,
|
|
154
|
+
label
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function parseAgentToolUiSurface(value) {
|
|
158
|
+
if (!isRecord(value) || value.schemaVersion !== AGENT_TOOL_UI_SCHEMA_VERSION)
|
|
159
|
+
return null;
|
|
160
|
+
if (!hasOnlyKeys(value, [
|
|
161
|
+
"actions",
|
|
162
|
+
"description",
|
|
163
|
+
"fields",
|
|
164
|
+
"id",
|
|
165
|
+
"operationId",
|
|
166
|
+
"requestId",
|
|
167
|
+
"schemaVersion",
|
|
168
|
+
"steps",
|
|
169
|
+
"submitLabel",
|
|
170
|
+
"title",
|
|
171
|
+
"toolSlug",
|
|
172
|
+
"values"
|
|
173
|
+
])) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
const id = boundedString(value.id, 200);
|
|
177
|
+
const title = boundedString(value.title, 200);
|
|
178
|
+
const toolSlug = boundedString(value.toolSlug, 200);
|
|
179
|
+
const description = value.description === void 0 ? void 0 : boundedString(value.description, 800);
|
|
180
|
+
const operationId = value.operationId === void 0 ? void 0 : boundedString(value.operationId, 200);
|
|
181
|
+
const requestId = value.requestId === void 0 ? void 0 : boundedString(value.requestId, 200);
|
|
182
|
+
const submitLabel = value.submitLabel === void 0 ? void 0 : boundedString(value.submitLabel, 80);
|
|
183
|
+
if (!id || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
const fields = value.fields.map(parseField);
|
|
187
|
+
if (fields.some((field) => field === null)) return null;
|
|
188
|
+
const steps = value.steps === void 0 ? void 0 : Array.isArray(value.steps) && value.steps.length <= 8 ? value.steps.map(parseStep) : null;
|
|
189
|
+
if (steps?.some((step) => step === null)) return null;
|
|
190
|
+
const actions = value.actions === void 0 ? void 0 : Array.isArray(value.actions) && value.actions.length <= 8 ? value.actions.map(parseAction) : null;
|
|
191
|
+
if (actions?.some((action) => action === null)) return null;
|
|
192
|
+
if (value.description !== void 0 && !description) return null;
|
|
193
|
+
if (value.operationId !== void 0 && !operationId) return null;
|
|
194
|
+
if (value.requestId !== void 0 && !requestId) return null;
|
|
195
|
+
if (value.submitLabel !== void 0 && !submitLabel) return null;
|
|
196
|
+
const values = value.values !== void 0 && isRecord(value.values) ? value.values : void 0;
|
|
197
|
+
if (value.values !== void 0) {
|
|
198
|
+
if (!values || !Object.values(values).every((item) => isJsonValue(item)))
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
schemaVersion: AGENT_TOOL_UI_SCHEMA_VERSION,
|
|
203
|
+
id,
|
|
204
|
+
title,
|
|
205
|
+
toolSlug,
|
|
206
|
+
fields,
|
|
207
|
+
...actions ? { actions } : {},
|
|
208
|
+
...description ? { description } : {},
|
|
209
|
+
...operationId ? { operationId } : {},
|
|
210
|
+
...requestId ? { requestId } : {},
|
|
211
|
+
...submitLabel ? { submitLabel } : {},
|
|
212
|
+
...steps ? { steps } : {},
|
|
213
|
+
...values ? { values } : {}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function hasOnlyKeys(value, allowed) {
|
|
217
|
+
const allowedKeys = new Set(allowed);
|
|
218
|
+
return Object.keys(value).every((key) => allowedKeys.has(key));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/runtime/tool-result-envelope.ts
|
|
222
|
+
var ENVELOPE_KEYS = /* @__PURE__ */ new Set([
|
|
223
|
+
"schemaVersion",
|
|
224
|
+
"output",
|
|
225
|
+
"presentationKinds",
|
|
226
|
+
"ui"
|
|
227
|
+
]);
|
|
228
|
+
function isRecord2(value) {
|
|
229
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
230
|
+
}
|
|
231
|
+
function isJsonValue2(value, seen = /* @__PURE__ */ new Set()) {
|
|
232
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
236
|
+
if (typeof value !== "object") return false;
|
|
237
|
+
if (seen.has(value)) return false;
|
|
238
|
+
seen.add(value);
|
|
239
|
+
const valid = Array.isArray(value) ? value.every((item) => isJsonValue2(item, seen)) : Object.entries(value).every(
|
|
240
|
+
([key, item]) => typeof key === "string" && isJsonValue2(item, seen)
|
|
241
|
+
);
|
|
242
|
+
seen.delete(value);
|
|
243
|
+
return valid;
|
|
244
|
+
}
|
|
245
|
+
function decodeEnvelope(value) {
|
|
246
|
+
if (typeof value !== "string") return value;
|
|
247
|
+
try {
|
|
248
|
+
return JSON.parse(value);
|
|
249
|
+
} catch {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function parseAgentToolResultEnvelope(value) {
|
|
254
|
+
const decoded = decodeEnvelope(value);
|
|
255
|
+
if (!isRecord2(decoded)) return null;
|
|
256
|
+
const keys = Object.keys(decoded);
|
|
257
|
+
if (keys.some((key) => !ENVELOPE_KEYS.has(key)) || decoded.schemaVersion !== "webless.tool-result.v1" || !isJsonValue2(decoded.output) || !Array.isArray(decoded.presentationKinds) || decoded.presentationKinds.length < 1 || decoded.presentationKinds.length > 16) {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
const presentationKinds = decoded.presentationKinds.flatMap((kind) => {
|
|
261
|
+
if (typeof kind !== "string") return [];
|
|
262
|
+
const normalized = kind.trim();
|
|
263
|
+
return normalized && normalized.length <= 128 ? [normalized] : [];
|
|
264
|
+
});
|
|
265
|
+
if (presentationKinds.length !== decoded.presentationKinds.length) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
const ui = decoded.ui === void 0 ? void 0 : parseAgentToolUiSurface(decoded.ui);
|
|
269
|
+
if (decoded.ui !== void 0 && !ui) return null;
|
|
270
|
+
return {
|
|
271
|
+
schemaVersion: "webless.tool-result.v1",
|
|
272
|
+
output: decoded.output,
|
|
273
|
+
presentationKinds,
|
|
274
|
+
...ui ? { ui } : {}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/react/lib/composer-form.ts
|
|
279
|
+
var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
280
|
+
var MIN_FORM_FIELDS = 2;
|
|
281
|
+
var MAX_FORM_FIELDS = 5;
|
|
282
|
+
var FIELD_LIBRARY = [
|
|
283
|
+
{
|
|
284
|
+
id: "email",
|
|
285
|
+
kind: "email",
|
|
286
|
+
label: "Email",
|
|
287
|
+
placeholder: "email@example.com",
|
|
288
|
+
required: true,
|
|
289
|
+
autocomplete: "email",
|
|
290
|
+
patterns: [/\be-?mails?\b/i]
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: "name",
|
|
294
|
+
kind: "text",
|
|
295
|
+
label: "Name",
|
|
296
|
+
placeholder: "Your name",
|
|
297
|
+
required: true,
|
|
298
|
+
autocomplete: "name",
|
|
299
|
+
patterns: [/\b((full|first|last)\s+)?names?\b/i],
|
|
300
|
+
exclude: /\bcompany\s+names?\b/i
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
id: "phone",
|
|
304
|
+
kind: "tel",
|
|
305
|
+
label: "Phone",
|
|
306
|
+
placeholder: "+1 555 0100",
|
|
307
|
+
required: true,
|
|
308
|
+
autocomplete: "tel",
|
|
309
|
+
patterns: [/\b(phone|mobile|cell)\b/i]
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: "company",
|
|
313
|
+
kind: "text",
|
|
314
|
+
label: "Company",
|
|
315
|
+
placeholder: "Company name",
|
|
316
|
+
required: true,
|
|
317
|
+
autocomplete: "organization",
|
|
318
|
+
patterns: [/\bcompan(y|ies)\b/i]
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
id: "message",
|
|
322
|
+
kind: "textarea",
|
|
323
|
+
label: "Message",
|
|
324
|
+
placeholder: "Message\u2026",
|
|
325
|
+
required: true,
|
|
326
|
+
patterns: [/\b(your message|a message|the message|inquiry|enquiry)\b/i],
|
|
327
|
+
exclude: /\bin one message\b/i
|
|
328
|
+
}
|
|
329
|
+
];
|
|
330
|
+
function asRecord(value) {
|
|
331
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
332
|
+
}
|
|
333
|
+
function asString(value) {
|
|
334
|
+
return typeof value === "string" ? value.trim() : "";
|
|
335
|
+
}
|
|
336
|
+
function isFieldKind2(value) {
|
|
337
|
+
return value === "text" || value === "email" || value === "tel" || value === "textarea";
|
|
338
|
+
}
|
|
339
|
+
function parseVisitorFormFields(value) {
|
|
340
|
+
if (!Array.isArray(value)) return [];
|
|
341
|
+
const fields = [];
|
|
342
|
+
const seen = /* @__PURE__ */ new Set();
|
|
343
|
+
for (const item of value) {
|
|
344
|
+
const record = asRecord(item);
|
|
345
|
+
const id = asString(record?.id).toLowerCase().replace(/[^a-z0-9_-]/g, "");
|
|
346
|
+
const kind = asString(record?.kind);
|
|
347
|
+
if (!record || !id || seen.has(id) || !isFieldKind2(kind)) continue;
|
|
348
|
+
seen.add(id);
|
|
349
|
+
const label = asString(record.label) || id;
|
|
350
|
+
fields.push({
|
|
351
|
+
id,
|
|
352
|
+
kind,
|
|
353
|
+
label,
|
|
354
|
+
placeholder: asString(record.placeholder) || label,
|
|
355
|
+
required: record.required !== false,
|
|
356
|
+
...asString(record.autocomplete) ? { autocomplete: asString(record.autocomplete) } : {}
|
|
357
|
+
});
|
|
358
|
+
if (fields.length >= MAX_FORM_FIELDS) break;
|
|
359
|
+
}
|
|
360
|
+
return fields;
|
|
361
|
+
}
|
|
362
|
+
function readComposerControlValue(event) {
|
|
363
|
+
const node = event.target ?? event.currentTarget ?? null;
|
|
364
|
+
if (node && typeof node === "object" && "value" in node && typeof node.value === "string") {
|
|
365
|
+
return node.value;
|
|
366
|
+
}
|
|
367
|
+
return "";
|
|
368
|
+
}
|
|
369
|
+
function isValidComposerFieldValue(field, value) {
|
|
370
|
+
const trimmed = value.trim();
|
|
371
|
+
if (!trimmed) return !field.required;
|
|
372
|
+
if (field.kind === "email") return EMAIL_PATTERN.test(trimmed);
|
|
373
|
+
if (field.kind === "tel") return trimmed.replace(/\D/g, "").length >= 7;
|
|
374
|
+
return trimmed.length > 0;
|
|
375
|
+
}
|
|
376
|
+
function isComposerFormComplete(form, values) {
|
|
377
|
+
return form.fields.every(
|
|
378
|
+
(field) => isValidComposerFieldValue(field, values[field.id] ?? "")
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
function formatComposerFormMessage(form, values) {
|
|
382
|
+
return form.fields.map((field) => {
|
|
383
|
+
const value = (values[field.id] ?? "").trim();
|
|
384
|
+
return value ? `${field.label}: ${value}` : "";
|
|
385
|
+
}).filter(Boolean).join("\n");
|
|
386
|
+
}
|
|
387
|
+
function looksLikeFieldCollection(text) {
|
|
388
|
+
return /\b(please|need|send|share|enter|provide|add|collect|what|which|use)\b/i.test(
|
|
389
|
+
text
|
|
390
|
+
) || /:\s*$/m.test(text) || /^[-*•]\s+/m.test(text);
|
|
391
|
+
}
|
|
392
|
+
function looksLikeBookingCopy(text) {
|
|
393
|
+
return /book(ing)? (card|a demo|this time)|pick a (date|time)|available (times|slots)|calendly/i.test(
|
|
394
|
+
text
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
function inferComposerForm(text) {
|
|
398
|
+
const cleaned = text.trim();
|
|
399
|
+
if (!cleaned || looksLikeBookingCopy(cleaned) || !looksLikeFieldCollection(cleaned)) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
const fields = FIELD_LIBRARY.flatMap((field) => {
|
|
403
|
+
if (field.exclude?.test(cleaned)) {
|
|
404
|
+
const leftover = cleaned.replace(field.exclude, " ");
|
|
405
|
+
if (!field.patterns.some((pattern) => pattern.test(leftover))) return [];
|
|
406
|
+
} else if (!field.patterns.some((pattern) => pattern.test(cleaned))) {
|
|
407
|
+
return [];
|
|
408
|
+
}
|
|
409
|
+
const { patterns: _patterns, exclude: _exclude, ...next } = field;
|
|
410
|
+
return [next];
|
|
411
|
+
}).slice(0, MAX_FORM_FIELDS);
|
|
412
|
+
if (fields.length < MIN_FORM_FIELDS) return null;
|
|
413
|
+
return {
|
|
414
|
+
id: `inferred:${fields.map((field) => field.id).join("+")}`,
|
|
415
|
+
fields
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function resolveComposerForm(input) {
|
|
419
|
+
if (input.enabled === false || input.hasBookingOffer) return null;
|
|
420
|
+
const text = input.agentText.trim();
|
|
421
|
+
if (!text) return null;
|
|
422
|
+
const card = input.cards?.find((item) => item.type === "visitor_form");
|
|
423
|
+
if (card?.fields && card.fields.length >= MIN_FORM_FIELDS) {
|
|
424
|
+
return {
|
|
425
|
+
id: `card:${card.fields.map((field) => field.id).join("+")}`,
|
|
426
|
+
fields: card.fields.slice(0, MAX_FORM_FIELDS)
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
return inferComposerForm(text);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/react/lib/tool-card.ts
|
|
433
|
+
function preferBookingOffer(current, next) {
|
|
434
|
+
if (!current) return next;
|
|
435
|
+
if (next.slots.length !== current.slots.length) {
|
|
436
|
+
return next.slots.length > current.slots.length ? next : current;
|
|
437
|
+
}
|
|
438
|
+
if (next.eventTypes.length !== current.eventTypes.length) {
|
|
439
|
+
return next.eventTypes.length > current.eventTypes.length ? next : current;
|
|
440
|
+
}
|
|
441
|
+
return next;
|
|
442
|
+
}
|
|
443
|
+
function looksLikeBookingReady(text) {
|
|
444
|
+
return /demo option|options are ready|pick a (date|time)|available (times|slots)|book(ing)? (card|the demo)|schedule/i.test(
|
|
445
|
+
text
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
function bookingOfferIdentityKey(offer) {
|
|
449
|
+
const eventTypes = offer.eventTypes.map(
|
|
450
|
+
(item) => `${item.uri}:${item.duration ?? ""}:${item.locationKind ?? ""}:${item.location ?? ""}`
|
|
451
|
+
).join("|");
|
|
452
|
+
const slots = offer.slots.map((slot) => `${slot.eventTypeUri ?? ""}:${slot.startTime}`).join("|");
|
|
453
|
+
return `${eventTypes}::${slots}` || "offer";
|
|
454
|
+
}
|
|
455
|
+
var FENCE_PATTERN = /```(?:webless-tool-card|json)\s*([\s\S]*?)```/gi;
|
|
456
|
+
function asRecord2(value) {
|
|
457
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
458
|
+
}
|
|
459
|
+
function asString2(value) {
|
|
460
|
+
return typeof value === "string" ? value.trim() : "";
|
|
461
|
+
}
|
|
462
|
+
function isEventUri(value) {
|
|
463
|
+
return /^https:\/\/api\.calendly\.com\/scheduled_events\/[^/]+$/i.test(value);
|
|
464
|
+
}
|
|
465
|
+
function isEventTypeUri(value) {
|
|
466
|
+
return /^https:\/\/api\.calendly\.com\/event_types\/[^/]+$/i.test(value);
|
|
467
|
+
}
|
|
468
|
+
function parseToolCard(value) {
|
|
469
|
+
const record = asRecord2(value);
|
|
470
|
+
if (!record) return null;
|
|
471
|
+
if (record.booking_offer && asString2(record.type) !== "booking_offer") {
|
|
472
|
+
const nested = parseToolCard(record.booking_offer);
|
|
473
|
+
if (nested) return nested;
|
|
474
|
+
}
|
|
475
|
+
if (record.visitor_booking && asString2(record.type) !== "booking_confirmed") {
|
|
476
|
+
const nested = parseToolCard(record.visitor_booking);
|
|
477
|
+
if (nested) return nested;
|
|
478
|
+
}
|
|
479
|
+
const type = asString2(record.type);
|
|
480
|
+
if (type === "booking_offer") {
|
|
481
|
+
const eventTypes = Array.isArray(record.eventTypes) ? record.eventTypes.flatMap((item) => {
|
|
482
|
+
const entry = asRecord2(item);
|
|
483
|
+
const uri = asString2(entry?.uri);
|
|
484
|
+
if (!entry || !uri) return [];
|
|
485
|
+
const duration = entry.duration;
|
|
486
|
+
const locationKind = asString2(entry.locationKind);
|
|
487
|
+
const location = asString2(entry.location);
|
|
488
|
+
return [
|
|
489
|
+
{
|
|
490
|
+
name: asString2(entry.name) || "Meeting",
|
|
491
|
+
uri,
|
|
492
|
+
...typeof duration === "number" ? { duration } : {},
|
|
493
|
+
...locationKind ? { locationKind } : {},
|
|
494
|
+
...location ? { location } : {}
|
|
495
|
+
}
|
|
496
|
+
];
|
|
497
|
+
}) : [];
|
|
498
|
+
const slots = Array.isArray(record.slots) ? record.slots.flatMap((item) => {
|
|
499
|
+
const entry = asRecord2(item);
|
|
500
|
+
const startTime = asString2(entry?.startTime);
|
|
501
|
+
if (!entry || !startTime) return [];
|
|
502
|
+
const eventTypeUri = asString2(entry.eventTypeUri);
|
|
503
|
+
return [
|
|
504
|
+
{
|
|
505
|
+
startTime,
|
|
506
|
+
...isEventTypeUri(eventTypeUri) ? { eventTypeUri } : {}
|
|
507
|
+
}
|
|
508
|
+
];
|
|
509
|
+
}) : [];
|
|
510
|
+
if (eventTypes.length === 0 && slots.length === 0) return null;
|
|
511
|
+
return { type: "booking_offer", eventTypes, slots };
|
|
512
|
+
}
|
|
513
|
+
if (type === "booking_confirmed") {
|
|
514
|
+
const eventUri = asString2(record.eventUri);
|
|
515
|
+
if (!isEventUri(eventUri)) return null;
|
|
516
|
+
const inviteeUri = asString2(record.inviteeUri);
|
|
517
|
+
const inviteeEmail = asString2(record.inviteeEmail);
|
|
518
|
+
const startTime = asString2(record.startTime);
|
|
519
|
+
return {
|
|
520
|
+
type: "booking_confirmed",
|
|
521
|
+
eventUri,
|
|
522
|
+
...inviteeUri ? { inviteeUri } : {},
|
|
523
|
+
...inviteeEmail ? { inviteeEmail } : {},
|
|
524
|
+
...startTime ? { startTime } : {}
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
if (type === "booking_canceled") {
|
|
528
|
+
const eventUri = asString2(record.eventUri);
|
|
529
|
+
if (!isEventUri(eventUri)) return null;
|
|
530
|
+
return { type: "booking_canceled", eventUri };
|
|
531
|
+
}
|
|
532
|
+
if (type === "visitor_form") {
|
|
533
|
+
const fields = parseVisitorFormFields(record.fields);
|
|
534
|
+
if (fields.length < 2) return null;
|
|
535
|
+
return { type: "visitor_form", fields };
|
|
536
|
+
}
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
function formatBookingOfferFence(offer) {
|
|
540
|
+
return [
|
|
541
|
+
"```webless-tool-card",
|
|
542
|
+
JSON.stringify({
|
|
543
|
+
type: "booking_offer",
|
|
544
|
+
eventTypes: offer.eventTypes,
|
|
545
|
+
slots: offer.slots
|
|
546
|
+
}),
|
|
547
|
+
"```"
|
|
548
|
+
].join("\n");
|
|
549
|
+
}
|
|
550
|
+
function bookingCardFromActionOutput(output) {
|
|
551
|
+
const record = asRecord2(output);
|
|
552
|
+
const data = asRecord2(record?.data) ?? record;
|
|
553
|
+
return parseToolCard(data);
|
|
554
|
+
}
|
|
555
|
+
function ensureBookingOfferText(text, offer) {
|
|
556
|
+
if (!offer) return text;
|
|
557
|
+
if (extractToolCards(text).some((card) => card.type === "booking_offer")) {
|
|
558
|
+
return text;
|
|
559
|
+
}
|
|
560
|
+
const visible = stripToolCards(text).trim() || text.trim();
|
|
561
|
+
return `${visible}
|
|
562
|
+
|
|
563
|
+
${formatBookingOfferFence(offer)}`;
|
|
564
|
+
}
|
|
565
|
+
function hideToolCardFences(text) {
|
|
566
|
+
return text.replace(/```(?:webless-tool-card|json)\s*[\s\S]*?```/gi, "").replace(/```(?:webless-tool-card|json)[\s\S]*$/i, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
567
|
+
}
|
|
568
|
+
var BOOKING_CARD_FALLBACK = "Pick a date and time that works for you.";
|
|
569
|
+
function looksLikeBookingAvailabilityDump(text) {
|
|
570
|
+
const cleaned = text.trim();
|
|
571
|
+
if (!cleaned) return false;
|
|
572
|
+
const isoCount = (cleaned.match(/\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/g) ?? []).length;
|
|
573
|
+
const utcCount = (cleaned.match(/\bUTC\b/g) ?? []).length;
|
|
574
|
+
return isoCount >= 2 || utcCount >= 2 || /api\.calendly\.com|calendly\.com\//i.test(cleaned) || /webless-tool-card/i.test(cleaned);
|
|
575
|
+
}
|
|
576
|
+
function sanitizeBookingOfferCopy(text) {
|
|
577
|
+
const cleaned = hideToolCardFences(text);
|
|
578
|
+
if (!cleaned || looksLikeBookingAvailabilityDump(cleaned)) {
|
|
579
|
+
return BOOKING_CARD_FALLBACK;
|
|
580
|
+
}
|
|
581
|
+
return cleaned;
|
|
582
|
+
}
|
|
583
|
+
function visitorTimeZone() {
|
|
584
|
+
try {
|
|
585
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
586
|
+
} catch {
|
|
587
|
+
return "UTC";
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
function extractToolCards(text) {
|
|
591
|
+
const cards = [];
|
|
592
|
+
for (const match of text.matchAll(FENCE_PATTERN)) {
|
|
593
|
+
try {
|
|
594
|
+
const card = parseToolCard(JSON.parse(match[1] ?? ""));
|
|
595
|
+
if (card) cards.push(card);
|
|
596
|
+
} catch {
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return cards;
|
|
600
|
+
}
|
|
601
|
+
function stripToolCards(text) {
|
|
602
|
+
return text.replace(FENCE_PATTERN, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
603
|
+
}
|
|
604
|
+
function localDateKey(date) {
|
|
605
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
606
|
+
return [
|
|
607
|
+
date.getFullYear(),
|
|
608
|
+
String(date.getMonth() + 1).padStart(2, "0"),
|
|
609
|
+
String(date.getDate()).padStart(2, "0")
|
|
610
|
+
].join("-");
|
|
611
|
+
}
|
|
612
|
+
function slotDateKey(startTime) {
|
|
613
|
+
return localDateKey(new Date(startTime)) || startTime;
|
|
614
|
+
}
|
|
615
|
+
function bookingSlotsForEventType(slots, eventTypeUri) {
|
|
616
|
+
return slots.filter(
|
|
617
|
+
(slot) => !eventTypeUri || !slot.eventTypeUri || slot.eventTypeUri === eventTypeUri
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
function firstAvailableBookingMonth(slots) {
|
|
621
|
+
let earliest;
|
|
622
|
+
for (const slot of slots) {
|
|
623
|
+
const key = slotDateKey(slot.startTime);
|
|
624
|
+
if (!earliest || key < earliest) earliest = key;
|
|
625
|
+
}
|
|
626
|
+
const [year, month] = (earliest ?? slotDateKey((/* @__PURE__ */ new Date()).toISOString())).split("-").map(Number);
|
|
627
|
+
if (!year || !month) {
|
|
628
|
+
const now = /* @__PURE__ */ new Date();
|
|
629
|
+
return { year: now.getFullYear(), month: now.getMonth() };
|
|
630
|
+
}
|
|
631
|
+
return { year, month: month - 1 };
|
|
632
|
+
}
|
|
633
|
+
function formatMonthTitle(year, month) {
|
|
634
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
635
|
+
month: "long",
|
|
636
|
+
year: "numeric"
|
|
637
|
+
}).format(new Date(year, month, 1));
|
|
638
|
+
}
|
|
639
|
+
function formatLongDate(startTime) {
|
|
640
|
+
const date = new Date(startTime);
|
|
641
|
+
if (Number.isNaN(date.getTime())) return startTime;
|
|
642
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
643
|
+
weekday: "long",
|
|
644
|
+
month: "long",
|
|
645
|
+
day: "numeric"
|
|
646
|
+
}).format(date);
|
|
647
|
+
}
|
|
648
|
+
function weekdayLabels() {
|
|
649
|
+
return Array.from(
|
|
650
|
+
{ length: 7 },
|
|
651
|
+
(_, index) => new Intl.DateTimeFormat(void 0, { weekday: "short" }).format(
|
|
652
|
+
new Date(2026, 7, 3 + index)
|
|
653
|
+
)
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
function formatTimeChip(startTime) {
|
|
657
|
+
const date = new Date(startTime);
|
|
658
|
+
if (Number.isNaN(date.getTime())) return startTime;
|
|
659
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
660
|
+
hour: "numeric",
|
|
661
|
+
minute: "2-digit"
|
|
662
|
+
}).format(date);
|
|
663
|
+
}
|
|
664
|
+
function formatSlotTimeZone(startTime) {
|
|
665
|
+
const date = new Date(startTime);
|
|
666
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
667
|
+
return new Intl.DateTimeFormat(void 0, { timeZoneName: "short" }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value ?? "";
|
|
668
|
+
}
|
|
669
|
+
function formatSlotLabel(startTime) {
|
|
670
|
+
const date = new Date(startTime);
|
|
671
|
+
if (Number.isNaN(date.getTime())) return startTime;
|
|
672
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
673
|
+
weekday: "short",
|
|
674
|
+
month: "short",
|
|
675
|
+
day: "numeric",
|
|
676
|
+
hour: "numeric",
|
|
677
|
+
minute: "2-digit",
|
|
678
|
+
timeZoneName: "short"
|
|
679
|
+
}).format(date);
|
|
680
|
+
}
|
|
681
|
+
function formatBookingRequest(input) {
|
|
682
|
+
return [
|
|
683
|
+
"Book this meeting now with CALENDLY_POST_INVITEE.",
|
|
684
|
+
"Execute CALENDLY_POST_INVITEE in this turn with the fields below.",
|
|
685
|
+
"Do not open a Calendly URL and do not list other scheduled events.",
|
|
686
|
+
"Do not invent a location kind. Use only the location fields below.",
|
|
687
|
+
`event_type: ${input.eventTypeUri}`,
|
|
688
|
+
`start_time: ${input.startTime}`,
|
|
689
|
+
`invitee.name: ${input.inviteeName}`,
|
|
690
|
+
`invitee.email: ${input.inviteeEmail}`,
|
|
691
|
+
`invitee.timezone: ${input.timezone}`,
|
|
692
|
+
...input.locationKind ? [
|
|
693
|
+
`location.kind: ${input.locationKind}`,
|
|
694
|
+
...input.location ? [`location.location: ${input.location}`] : []
|
|
695
|
+
] : ["Do not send a location field."],
|
|
696
|
+
"After it succeeds, reply with one short confirmation and a webless-tool-card booking_confirmed block using visitor_booking."
|
|
697
|
+
].join("\n");
|
|
698
|
+
}
|
|
699
|
+
function visitorBookingPrefix(booking) {
|
|
700
|
+
return [
|
|
701
|
+
"This visitor already booked a meeting. Use only this meeting:",
|
|
702
|
+
`- scheduled event URI: ${booking.eventUri}`,
|
|
703
|
+
...booking.inviteeUri ? [`- invitee URI: ${booking.inviteeUri}`] : [],
|
|
704
|
+
...booking.inviteeEmail ? [`- invitee email: ${booking.inviteeEmail}`] : [],
|
|
705
|
+
"For details call CALENDLY_GET_EVENT or CALENDLY_GET_EVENT_INVITEE with those URIs.",
|
|
706
|
+
"If you must list events, pass this invitee_email. Never describe any other scheduled event.",
|
|
707
|
+
"start_time values from Calendly are UTC."
|
|
708
|
+
].join("\n");
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// src/react/lib/tool-result.ts
|
|
712
|
+
var MAX_TEXT_LENGTH = 240;
|
|
713
|
+
var MAX_DETAILS = 6;
|
|
714
|
+
var MAX_LINKS = 4;
|
|
715
|
+
var MAX_COLLECTION_ITEMS = 8;
|
|
716
|
+
var INTERNAL_FACT_LABEL = /^(hs\b|createdate|created at|updatedate|updated at|firstname|first name|lastname|last name|vid|objectid|object id|all contact)/iu;
|
|
717
|
+
function safeText(value) {
|
|
718
|
+
if (typeof value !== "string") return "";
|
|
719
|
+
return value.trim().slice(0, MAX_TEXT_LENGTH);
|
|
720
|
+
}
|
|
721
|
+
function safeHref(value) {
|
|
722
|
+
const text = safeText(value);
|
|
723
|
+
if (!text) return "";
|
|
724
|
+
try {
|
|
725
|
+
const url = new URL(text);
|
|
726
|
+
return url.protocol === "https:" ? url.toString() : "";
|
|
727
|
+
} catch {
|
|
728
|
+
return "";
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
function fallbackTitle(result) {
|
|
732
|
+
if (result.status === "failed") return "Couldn\u2019t complete this action";
|
|
733
|
+
if (result.status === "rejected") return "Action not approved";
|
|
734
|
+
return "Action completed";
|
|
735
|
+
}
|
|
736
|
+
function bookingPresenter(kind) {
|
|
737
|
+
return {
|
|
738
|
+
kind,
|
|
739
|
+
present: ({ envelope }) => {
|
|
740
|
+
const card = bookingCardFromActionOutput(envelope.output);
|
|
741
|
+
return card && card.type === kind ? { kind: "booking", card } : null;
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
function asRecord3(value) {
|
|
746
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
747
|
+
}
|
|
748
|
+
function parseFacts(value) {
|
|
749
|
+
if (!Array.isArray(value)) return [];
|
|
750
|
+
return value.flatMap((item) => {
|
|
751
|
+
const fact = asRecord3(item);
|
|
752
|
+
const label = safeText(fact?.label);
|
|
753
|
+
const factValue = safeText(fact?.value);
|
|
754
|
+
if (!label || !factValue) return [];
|
|
755
|
+
if (INTERNAL_FACT_LABEL.test(label)) return [];
|
|
756
|
+
return [{ label, value: factValue }];
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
function parseLinks(output, defaultLabel) {
|
|
760
|
+
const href = safeHref(output.href);
|
|
761
|
+
const linkLabel = safeText(output.linkLabel) || defaultLabel;
|
|
762
|
+
return href ? [{ label: linkLabel, href }] : [];
|
|
763
|
+
}
|
|
764
|
+
function entityResultPresenter() {
|
|
765
|
+
return {
|
|
766
|
+
kind: "entity_result",
|
|
767
|
+
present: ({ envelope, result }) => {
|
|
768
|
+
const output = asRecord3(envelope.output);
|
|
769
|
+
if (!output) return null;
|
|
770
|
+
const title = safeText(output.title) || fallbackTitle(result);
|
|
771
|
+
const description = safeText(output.description);
|
|
772
|
+
const facts = parseFacts(output.facts).filter((fact) => fact.value !== description && fact.value !== title).slice(0, MAX_DETAILS);
|
|
773
|
+
return {
|
|
774
|
+
kind: "entity",
|
|
775
|
+
title,
|
|
776
|
+
...description && description !== title ? { description } : {},
|
|
777
|
+
...facts.length ? { details: facts } : {},
|
|
778
|
+
...parseLinks(output, "Open record").length ? { links: parseLinks(output, "Open record") } : {}
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
function collectionResultPresenter() {
|
|
784
|
+
return {
|
|
785
|
+
kind: "collection",
|
|
786
|
+
present: ({ envelope, result }) => {
|
|
787
|
+
const output = asRecord3(envelope.output);
|
|
788
|
+
if (!output) return null;
|
|
789
|
+
const title = safeText(output.title) || fallbackTitleFromKind("collection");
|
|
790
|
+
const items = Array.isArray(output.items) ? output.items.slice(0, MAX_COLLECTION_ITEMS).flatMap((item) => {
|
|
791
|
+
const entry = asRecord3(item);
|
|
792
|
+
if (!entry) return [];
|
|
793
|
+
const itemTitle = safeText(entry.title);
|
|
794
|
+
if (!itemTitle) return [];
|
|
795
|
+
const description = safeText(entry.description);
|
|
796
|
+
const details = parseFacts(entry.facts).slice(0, MAX_DETAILS);
|
|
797
|
+
const href = safeHref(entry.href);
|
|
798
|
+
return [
|
|
799
|
+
{
|
|
800
|
+
title: itemTitle,
|
|
801
|
+
...description ? { description } : {},
|
|
802
|
+
...details.length ? { details } : {},
|
|
803
|
+
...href ? { href } : {}
|
|
804
|
+
}
|
|
805
|
+
];
|
|
806
|
+
}) : [];
|
|
807
|
+
return {
|
|
808
|
+
kind: "collection",
|
|
809
|
+
title,
|
|
810
|
+
items
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
function signatureResultPresenter() {
|
|
816
|
+
return {
|
|
817
|
+
kind: "signature",
|
|
818
|
+
present: ({ envelope, result }) => {
|
|
819
|
+
const output = asRecord3(envelope.output);
|
|
820
|
+
if (!output) return null;
|
|
821
|
+
const title = safeText(output.title) || fallbackTitleFromKind("signature");
|
|
822
|
+
const description = safeText(output.description);
|
|
823
|
+
const statusLabel = safeText(output.status);
|
|
824
|
+
return {
|
|
825
|
+
kind: "signature",
|
|
826
|
+
title,
|
|
827
|
+
...description ? { description } : {},
|
|
828
|
+
...statusLabel ? { statusLabel } : {},
|
|
829
|
+
...parseLinks(output, "Sign now").length ? { links: parseLinks(output, "Sign now") } : {}
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
function resultCardPresenter(kind) {
|
|
835
|
+
return {
|
|
836
|
+
kind,
|
|
837
|
+
present: ({ envelope, result }) => {
|
|
838
|
+
const output = asRecord3(envelope.output);
|
|
839
|
+
if (!output) return null;
|
|
840
|
+
const title = safeText(output.title) || fallbackTitleFromKind(kind);
|
|
841
|
+
const description = safeText(output.description);
|
|
842
|
+
const facts = parseFacts(output.facts).slice(0, MAX_DETAILS);
|
|
843
|
+
return {
|
|
844
|
+
kind: "summary",
|
|
845
|
+
title,
|
|
846
|
+
...description && description !== title ? { description } : {},
|
|
847
|
+
...facts.length ? { details: facts } : {},
|
|
848
|
+
...parseLinks(output, "Open").length ? { links: parseLinks(output, "Open") } : {}
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
function fallbackTitleFromKind(kind) {
|
|
854
|
+
if (kind === "document") return "Document ready";
|
|
855
|
+
if (kind === "signature") return "Ready to sign";
|
|
856
|
+
if (kind === "payment") return "Payment link";
|
|
857
|
+
if (kind === "collection") return "Results";
|
|
858
|
+
if (kind === "confirmation_result") return "Confirmed";
|
|
859
|
+
return "Saved";
|
|
860
|
+
}
|
|
861
|
+
var builtInVisitorToolResultRegistry = [
|
|
862
|
+
bookingPresenter("booking_offer"),
|
|
863
|
+
bookingPresenter("booking_confirmed"),
|
|
864
|
+
bookingPresenter("booking_canceled"),
|
|
865
|
+
entityResultPresenter(),
|
|
866
|
+
collectionResultPresenter(),
|
|
867
|
+
signatureResultPresenter(),
|
|
868
|
+
resultCardPresenter("document"),
|
|
869
|
+
resultCardPresenter("payment"),
|
|
870
|
+
resultCardPresenter("confirmation_result")
|
|
871
|
+
];
|
|
872
|
+
function resolvePresenterOutput(result, envelope, registry) {
|
|
873
|
+
const presenters = [...registry, ...builtInVisitorToolResultRegistry];
|
|
874
|
+
for (const presentationKind of envelope.presentationKinds) {
|
|
875
|
+
const presenter = presenters.find(
|
|
876
|
+
(candidate) => candidate.kind === presentationKind
|
|
877
|
+
);
|
|
878
|
+
if (!presenter) continue;
|
|
879
|
+
try {
|
|
880
|
+
const presented = presenter.present({
|
|
881
|
+
envelope,
|
|
882
|
+
presentationKind,
|
|
883
|
+
result
|
|
884
|
+
});
|
|
885
|
+
if (presented) return presented;
|
|
886
|
+
} catch {
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
return null;
|
|
891
|
+
}
|
|
892
|
+
function finalizeSummaryPresentation(result, proposed) {
|
|
893
|
+
const title = safeText(proposed.title) || fallbackTitle(result);
|
|
894
|
+
const description = safeText(proposed.description);
|
|
895
|
+
const details = proposed.details?.slice(0, MAX_DETAILS).flatMap((detail) => {
|
|
896
|
+
const label = safeText(detail.label);
|
|
897
|
+
const value = safeText(detail.value);
|
|
898
|
+
if (!label || !value) return [];
|
|
899
|
+
if (INTERNAL_FACT_LABEL.test(label)) return [];
|
|
900
|
+
if (value === title || value === description) return [];
|
|
901
|
+
return [{ label, value }];
|
|
902
|
+
});
|
|
903
|
+
const links = proposed.links?.slice(0, MAX_LINKS).flatMap((link) => {
|
|
904
|
+
const label = safeText(link.label);
|
|
905
|
+
const href = safeHref(link.href);
|
|
906
|
+
return label && href ? [{ label, href }] : [];
|
|
907
|
+
});
|
|
908
|
+
return {
|
|
909
|
+
id: result.callId,
|
|
910
|
+
toolName: result.toolName,
|
|
911
|
+
status: result.status,
|
|
912
|
+
kind: "summary",
|
|
913
|
+
title,
|
|
914
|
+
...description && description !== title ? { description } : {},
|
|
915
|
+
...details?.length ? { details } : {},
|
|
916
|
+
...links?.length ? { links } : {}
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
function presentVisitorToolResult(result, registry = []) {
|
|
920
|
+
const envelope = parseAgentToolResultEnvelope(result.output) ?? legacyBookingEnvelope(result.output);
|
|
921
|
+
const proposed = envelope ? resolvePresenterOutput(result, envelope, registry) : null;
|
|
922
|
+
if (proposed?.kind === "booking") {
|
|
923
|
+
return {
|
|
924
|
+
id: result.callId,
|
|
925
|
+
toolName: result.toolName,
|
|
926
|
+
status: result.status,
|
|
927
|
+
kind: "booking",
|
|
928
|
+
card: proposed.card
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
if (envelope?.ui && envelope.presentationKinds.includes("tool_input")) {
|
|
932
|
+
return {
|
|
933
|
+
id: result.callId,
|
|
934
|
+
toolName: result.toolName,
|
|
935
|
+
status: result.status,
|
|
936
|
+
kind: "hidden"
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
if (!proposed) {
|
|
940
|
+
if (result.status === "failed" || result.status === "rejected") {
|
|
941
|
+
return {
|
|
942
|
+
id: result.callId,
|
|
943
|
+
toolName: result.toolName,
|
|
944
|
+
status: result.status,
|
|
945
|
+
kind: "summary",
|
|
946
|
+
title: fallbackTitle(result)
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
return {
|
|
950
|
+
id: result.callId,
|
|
951
|
+
toolName: result.toolName,
|
|
952
|
+
status: result.status,
|
|
953
|
+
kind: "hidden"
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
if (proposed.kind === "entity") {
|
|
957
|
+
return {
|
|
958
|
+
id: result.callId,
|
|
959
|
+
toolName: result.toolName,
|
|
960
|
+
status: result.status,
|
|
961
|
+
...proposed
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
if (proposed.kind === "collection") {
|
|
965
|
+
return {
|
|
966
|
+
id: result.callId,
|
|
967
|
+
toolName: result.toolName,
|
|
968
|
+
status: result.status,
|
|
969
|
+
...proposed
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
if (proposed.kind === "signature") {
|
|
973
|
+
return {
|
|
974
|
+
id: result.callId,
|
|
975
|
+
toolName: result.toolName,
|
|
976
|
+
status: result.status,
|
|
977
|
+
...proposed
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
return finalizeSummaryPresentation(result, proposed);
|
|
981
|
+
}
|
|
982
|
+
function legacyBookingEnvelope(output) {
|
|
983
|
+
const card = bookingCardFromActionOutput(output);
|
|
984
|
+
return card ? {
|
|
985
|
+
schemaVersion: "webless.tool-result.v1",
|
|
986
|
+
output: card,
|
|
987
|
+
presentationKinds: [card.type]
|
|
988
|
+
} : null;
|
|
989
|
+
}
|
|
990
|
+
|
|
1
991
|
// src/react/hooks/useAgentChat.ts
|
|
2
992
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
993
|
|
|
@@ -10,11 +1000,31 @@ import {
|
|
|
10
1000
|
// src/runtime/capability.ts
|
|
11
1001
|
import { ClientError } from "eve/client";
|
|
12
1002
|
var MAX_REFRESH_SKEW_MS = 3e4;
|
|
13
|
-
|
|
1003
|
+
var LOCAL_LOOPBACK_ORIGINS = [
|
|
1004
|
+
"http://127.0.0.1:3010",
|
|
1005
|
+
"http://127.0.0.1:3001"
|
|
1006
|
+
];
|
|
1007
|
+
function isLoopbackRuntimeOrigin(origin) {
|
|
1008
|
+
try {
|
|
1009
|
+
const host = new URL(origin).hostname;
|
|
1010
|
+
return host === "127.0.0.1" || host === "localhost";
|
|
1011
|
+
} catch {
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
function localBootstrapOrigins(origin) {
|
|
1016
|
+
const normalized = origin.replace(/\/$/, "");
|
|
1017
|
+
if (!isLoopbackRuntimeOrigin(normalized)) return [normalized];
|
|
1018
|
+
return [
|
|
1019
|
+
normalized,
|
|
1020
|
+
...LOCAL_LOOPBACK_ORIGINS.filter((candidate) => candidate !== normalized)
|
|
1021
|
+
];
|
|
1022
|
+
}
|
|
1023
|
+
function isRecord3(value) {
|
|
14
1024
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15
1025
|
}
|
|
16
1026
|
function parseBootstrapResponse(value, indexId, now) {
|
|
17
|
-
if (!
|
|
1027
|
+
if (!isRecord3(value) || value.apiVersion !== "webless.ai/agent-runtime-bootstrap/v1" || typeof value.accessToken !== "string" || !value.accessToken || typeof value.expiresAt !== "string" || !isRecord3(value.identity) || value.identity.indexId !== indexId || typeof value.identity.revision !== "string" || !value.identity.revision || typeof value.identity.tenantId !== "string" || !value.identity.tenantId || typeof value.origin !== "string" || !value.origin || value.tokenType !== "Bearer" || typeof value.visitorSubject !== "string" || !value.visitorSubject) {
|
|
18
1028
|
throw new Error("Agent Runtime returned an invalid access response.");
|
|
19
1029
|
}
|
|
20
1030
|
const expiresAt = Date.parse(value.expiresAt);
|
|
@@ -34,7 +1044,7 @@ async function readBootstrapError(response) {
|
|
|
34
1044
|
const fallback = `Agent Runtime is unavailable (${response.status}).`;
|
|
35
1045
|
try {
|
|
36
1046
|
const value = await response.json();
|
|
37
|
-
return
|
|
1047
|
+
return isRecord3(value) && typeof value.error === "string" && value.error ? value.error : fallback;
|
|
38
1048
|
} catch {
|
|
39
1049
|
return fallback;
|
|
40
1050
|
}
|
|
@@ -56,20 +1066,31 @@ function createAgentRuntimeCapability(options) {
|
|
|
56
1066
|
);
|
|
57
1067
|
}
|
|
58
1068
|
}
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
1069
|
+
const bootstrapBody = JSON.stringify({
|
|
1070
|
+
clientSessionId: options.visitorSessionId,
|
|
1071
|
+
indexId: options.indexId,
|
|
1072
|
+
...previewBuildId ? { previewBuildId } : {},
|
|
1073
|
+
...previewGrant ? { previewGrant } : {},
|
|
1074
|
+
version: options.version
|
|
1075
|
+
});
|
|
1076
|
+
const postBootstrap = (origin) => fetchImplementation(`${origin}/webless/v1/bootstrap`, {
|
|
1077
|
+
body: bootstrapBody,
|
|
1078
|
+
headers: { "content-type": "application/json" },
|
|
1079
|
+
method: "POST"
|
|
1080
|
+
});
|
|
1081
|
+
let response;
|
|
1082
|
+
let lastError;
|
|
1083
|
+
for (const origin of localBootstrapOrigins(options.runtimeOrigin)) {
|
|
1084
|
+
try {
|
|
1085
|
+
response = await postBootstrap(origin);
|
|
1086
|
+
break;
|
|
1087
|
+
} catch (error) {
|
|
1088
|
+
lastError = error;
|
|
71
1089
|
}
|
|
72
|
-
|
|
1090
|
+
}
|
|
1091
|
+
if (!response) {
|
|
1092
|
+
throw lastError instanceof Error ? lastError : new Error("Agent Runtime is unavailable.");
|
|
1093
|
+
}
|
|
73
1094
|
if (!response.ok) {
|
|
74
1095
|
throw new Error(await readBootstrapError(response));
|
|
75
1096
|
}
|
|
@@ -243,12 +1264,8 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
|
|
|
243
1264
|
if (event.type === "message.completed") {
|
|
244
1265
|
handlers.onComplete?.();
|
|
245
1266
|
}
|
|
246
|
-
if (event.type === "action.result")
|
|
247
|
-
|
|
248
|
-
if (result && typeof result === "object" && "output" in result) {
|
|
249
|
-
handlers.onActionResult?.(result.output);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
1267
|
+
if (event.type === "action.result") emitActionResult(event, handlers);
|
|
1268
|
+
if (event.type === "input.requested") emitInputRequests(event, handlers);
|
|
252
1269
|
if (event.type !== "message.appended") return rendered;
|
|
253
1270
|
const { messageDelta, messageSoFar } = event.data;
|
|
254
1271
|
let delta = messageDelta;
|
|
@@ -259,9 +1276,45 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
|
|
|
259
1276
|
} else if (messageDelta) {
|
|
260
1277
|
next += messageDelta;
|
|
261
1278
|
}
|
|
262
|
-
if (delta) handlers.onDelta(delta);
|
|
1279
|
+
if (delta) handlers.onDelta?.(delta);
|
|
263
1280
|
return next;
|
|
264
1281
|
}
|
|
1282
|
+
function emitActionResult(event, handlers) {
|
|
1283
|
+
const result = event.data.result;
|
|
1284
|
+
if (result.kind === "tool-result") {
|
|
1285
|
+
handlers.onToolResult?.({
|
|
1286
|
+
callId: result.callId,
|
|
1287
|
+
toolName: result.toolName,
|
|
1288
|
+
status: event.data.status,
|
|
1289
|
+
output: result.output,
|
|
1290
|
+
...event.data.error ? { error: event.data.error } : {}
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
if ("output" in result) handlers.onActionResult?.(result.output);
|
|
1294
|
+
}
|
|
1295
|
+
function emitInputRequests(event, handlers) {
|
|
1296
|
+
handlers.onInputRequest?.(
|
|
1297
|
+
event.data.requests.map((request) => {
|
|
1298
|
+
const ui = parseAgentToolUiSurface(
|
|
1299
|
+
request.ui
|
|
1300
|
+
);
|
|
1301
|
+
return {
|
|
1302
|
+
requestId: request.requestId,
|
|
1303
|
+
kind: request.kind,
|
|
1304
|
+
prompt: request.prompt,
|
|
1305
|
+
...request.display ? { display: request.display } : {},
|
|
1306
|
+
...request.allowFreeform !== void 0 ? { allowFreeform: request.allowFreeform } : {},
|
|
1307
|
+
...request.options ? { options: request.options } : {},
|
|
1308
|
+
...ui ? { ui } : {},
|
|
1309
|
+
action: {
|
|
1310
|
+
callId: request.action.callId,
|
|
1311
|
+
kind: "tool-call",
|
|
1312
|
+
toolName: request.action.toolName
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
})
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
265
1318
|
function isResumeTurnMessage(received, candidate) {
|
|
266
1319
|
if (received === candidate) return true;
|
|
267
1320
|
return Boolean(candidate) && received.endsWith(`
|
|
@@ -537,9 +1590,11 @@ var AgentSession = class {
|
|
|
537
1590
|
let streamIndex = session?.state.streamIndex ?? 0;
|
|
538
1591
|
let rendered = "";
|
|
539
1592
|
const workItems = /* @__PURE__ */ new Map();
|
|
1593
|
+
let requestedInput = false;
|
|
540
1594
|
try {
|
|
541
1595
|
for await (const event of response) {
|
|
542
1596
|
if (signal.aborted) break;
|
|
1597
|
+
if (event.type === "input.requested") requestedInput = true;
|
|
543
1598
|
rendered = applyMessageEvent(event, rendered, handlers, workItems);
|
|
544
1599
|
streamIndex += 1;
|
|
545
1600
|
if (session) {
|
|
@@ -557,7 +1612,7 @@ var AgentSession = class {
|
|
|
557
1612
|
this.persistSessionCursor(session);
|
|
558
1613
|
}
|
|
559
1614
|
}
|
|
560
|
-
if (!rendered.trim() && !signal.aborted) {
|
|
1615
|
+
if (!rendered.trim() && !signal.aborted && !requestedInput) {
|
|
561
1616
|
throw new Error("Empty response from runtime");
|
|
562
1617
|
}
|
|
563
1618
|
return rendered.trim();
|
|
@@ -577,6 +1632,9 @@ var AgentSession = class {
|
|
|
577
1632
|
() => attached.snapshot({ signal })
|
|
578
1633
|
);
|
|
579
1634
|
const turnEvents = latestTurnEvents(snapshot.events);
|
|
1635
|
+
const hasInputRequest = turnEvents.some(
|
|
1636
|
+
(event) => event.type === "input.requested"
|
|
1637
|
+
);
|
|
580
1638
|
const received = turnEvents[0];
|
|
581
1639
|
const lastSent = persisted.lastMessage;
|
|
582
1640
|
const inFlight = !turnEvents.some((event) => isTurnBoundary(event));
|
|
@@ -587,6 +1645,8 @@ var AgentSession = class {
|
|
|
587
1645
|
const workItems = /* @__PURE__ */ new Map();
|
|
588
1646
|
for (const event of turnEvents) {
|
|
589
1647
|
applyWorkEvent(event, handlers, workItems);
|
|
1648
|
+
if (event.type === "input.requested") emitInputRequests(event, handlers);
|
|
1649
|
+
if (event.type === "action.result") emitActionResult(event, handlers);
|
|
590
1650
|
}
|
|
591
1651
|
if (rendered.startsWith(initialText)) {
|
|
592
1652
|
const missedText = rendered.slice(initialText.length);
|
|
@@ -616,7 +1676,9 @@ var AgentSession = class {
|
|
|
616
1676
|
);
|
|
617
1677
|
}
|
|
618
1678
|
handlers.onComplete?.();
|
|
619
|
-
if (!rendered.trim()
|
|
1679
|
+
if (!rendered.trim() && !hasInputRequest) {
|
|
1680
|
+
throw new Error("Empty response from runtime");
|
|
1681
|
+
}
|
|
620
1682
|
return rendered.trim();
|
|
621
1683
|
}
|
|
622
1684
|
let streamIndex = snapshot.session.streamIndex;
|
|
@@ -632,10 +1694,58 @@ var AgentSession = class {
|
|
|
632
1694
|
);
|
|
633
1695
|
if (isTurnBoundary(event)) break;
|
|
634
1696
|
}
|
|
635
|
-
session = client.sessions.attach(session.state.sessionId, { streamIndex });
|
|
636
|
-
this.session = session;
|
|
637
|
-
this.persistSessionCursor(session);
|
|
638
|
-
if (!rendered.trim() && !signal.aborted) {
|
|
1697
|
+
session = client.sessions.attach(session.state.sessionId, { streamIndex });
|
|
1698
|
+
this.session = session;
|
|
1699
|
+
this.persistSessionCursor(session);
|
|
1700
|
+
if (!rendered.trim() && !signal.aborted && !hasInputRequest) {
|
|
1701
|
+
throw new Error("Empty response from runtime");
|
|
1702
|
+
}
|
|
1703
|
+
return rendered.trim();
|
|
1704
|
+
}
|
|
1705
|
+
async respondTurn(responses, signal, handlers) {
|
|
1706
|
+
const client = this.ensureClient();
|
|
1707
|
+
const session = this.session ?? this.attachPersistedSession(client);
|
|
1708
|
+
if (!session) {
|
|
1709
|
+
throw new Error("No active session is waiting for input.");
|
|
1710
|
+
}
|
|
1711
|
+
this.session = session;
|
|
1712
|
+
const inputResponses = responses.map(
|
|
1713
|
+
({ requestId, optionId, text }) => ({
|
|
1714
|
+
requestId,
|
|
1715
|
+
...optionId ? { optionId } : {},
|
|
1716
|
+
...text ? { text } : {}
|
|
1717
|
+
})
|
|
1718
|
+
);
|
|
1719
|
+
const response = await withCapabilityRefresh(
|
|
1720
|
+
this.capability,
|
|
1721
|
+
() => session.respond(inputResponses, { signal })
|
|
1722
|
+
);
|
|
1723
|
+
this.activeResponse = response;
|
|
1724
|
+
let streamIndex = session.state.streamIndex;
|
|
1725
|
+
let rendered = "";
|
|
1726
|
+
let requestedInput = false;
|
|
1727
|
+
const workItems = /* @__PURE__ */ new Map();
|
|
1728
|
+
try {
|
|
1729
|
+
for await (const event of response) {
|
|
1730
|
+
if (signal.aborted) break;
|
|
1731
|
+
if (event.type === "input.requested") requestedInput = true;
|
|
1732
|
+
rendered = applyMessageEvent(event, rendered, handlers, workItems);
|
|
1733
|
+
streamIndex += 1;
|
|
1734
|
+
savePersistedAgentSession(
|
|
1735
|
+
this.visitorSessionId,
|
|
1736
|
+
session.state.sessionId,
|
|
1737
|
+
streamIndex,
|
|
1738
|
+
this.storeOptions
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
} finally {
|
|
1742
|
+
this.activeResponse = void 0;
|
|
1743
|
+
this.session = client.sessions.attach(session.state.sessionId, {
|
|
1744
|
+
streamIndex
|
|
1745
|
+
});
|
|
1746
|
+
this.persistSessionCursor(this.session);
|
|
1747
|
+
}
|
|
1748
|
+
if (!rendered.trim() && !signal.aborted && !requestedInput) {
|
|
639
1749
|
throw new Error("Empty response from runtime");
|
|
640
1750
|
}
|
|
641
1751
|
return rendered.trim();
|
|
@@ -695,297 +1805,62 @@ function createAgentClient(options) {
|
|
|
695
1805
|
resumeOptions.handlers,
|
|
696
1806
|
resumeOptions.initialText
|
|
697
1807
|
),
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
var PREVIEW_AUTHORIZATION_ERROR_MESSAGE = "This preview could not be authorized. Open the latest preview from Webless.";
|
|
708
|
-
function isTransientRuntimeMessage(message) {
|
|
709
|
-
const normalized = message.trim().toLowerCase();
|
|
710
|
-
return normalized.includes("empty response from runtime") || normalized.includes("failed to fetch") || normalized.includes("networkerror") || normalized.includes("load failed");
|
|
711
|
-
}
|
|
712
|
-
function isPreviewAuthorizationMessage(message) {
|
|
713
|
-
const normalized = message.trim().toLowerCase();
|
|
714
|
-
return normalized.includes("unpublished preview authorization") || normalized.includes("unpublished preview grant") || normalized.includes("agent studio preview grant") || normalized.includes("preview authorization");
|
|
715
|
-
}
|
|
716
|
-
function formatAgentError(error) {
|
|
717
|
-
if (error instanceof ClientError3) {
|
|
718
|
-
if (error.status === 401 && error.code === "index_required") {
|
|
719
|
-
return "Missing indexId \u2014 pass a published index id to createAgentClient().";
|
|
720
|
-
}
|
|
721
|
-
if (error.status === 403 && error.code === "agent_unavailable") {
|
|
722
|
-
return "Agent unavailable for this index (disabled or unpublished).";
|
|
723
|
-
}
|
|
724
|
-
if (error.status === 409 && error.code === "session_not_active") {
|
|
725
|
-
return "Session expired \u2014 send a new message to start again.";
|
|
726
|
-
}
|
|
727
|
-
if (error.message && isPreviewAuthorizationMessage(error.message)) {
|
|
728
|
-
return PREVIEW_AUTHORIZATION_ERROR_MESSAGE;
|
|
729
|
-
}
|
|
730
|
-
if (error.status >= 500 || error.message && isTransientRuntimeMessage(error.message)) {
|
|
731
|
-
return TRANSIENT_AGENT_ERROR_MESSAGE;
|
|
732
|
-
}
|
|
733
|
-
return error.message || "This assistant is unavailable right now.";
|
|
734
|
-
}
|
|
735
|
-
if (error instanceof DOMException && error.name === "AbortError") {
|
|
736
|
-
return "";
|
|
737
|
-
}
|
|
738
|
-
if (error instanceof Error) {
|
|
739
|
-
if (isPreviewAuthorizationMessage(error.message)) {
|
|
740
|
-
return PREVIEW_AUTHORIZATION_ERROR_MESSAGE;
|
|
741
|
-
}
|
|
742
|
-
return isTransientRuntimeMessage(error.message) ? TRANSIENT_AGENT_ERROR_MESSAGE : error.message;
|
|
743
|
-
}
|
|
744
|
-
return TRANSIENT_AGENT_ERROR_MESSAGE;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// src/react/lib/tool-card.ts
|
|
748
|
-
function bookingOfferIdentityKey(offer) {
|
|
749
|
-
const eventTypes = offer.eventTypes.map(
|
|
750
|
-
(item) => `${item.uri}:${item.duration ?? ""}:${item.locationKind ?? ""}:${item.location ?? ""}`
|
|
751
|
-
).join("|");
|
|
752
|
-
const slots = offer.slots.map((slot) => `${slot.eventTypeUri ?? ""}:${slot.startTime}`).join("|");
|
|
753
|
-
return `${eventTypes}::${slots}` || "offer";
|
|
754
|
-
}
|
|
755
|
-
var FENCE_PATTERN = /```(?:webless-tool-card|json)\s*([\s\S]*?)```/gi;
|
|
756
|
-
function asRecord(value) {
|
|
757
|
-
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
758
|
-
}
|
|
759
|
-
function asString(value) {
|
|
760
|
-
return typeof value === "string" ? value.trim() : "";
|
|
761
|
-
}
|
|
762
|
-
function isEventUri(value) {
|
|
763
|
-
return /^https:\/\/api\.calendly\.com\/scheduled_events\/[^/]+$/i.test(value);
|
|
764
|
-
}
|
|
765
|
-
function isEventTypeUri(value) {
|
|
766
|
-
return /^https:\/\/api\.calendly\.com\/event_types\/[^/]+$/i.test(value);
|
|
767
|
-
}
|
|
768
|
-
function parseToolCard(value) {
|
|
769
|
-
const record = asRecord(value);
|
|
770
|
-
if (!record) return null;
|
|
771
|
-
if (record.booking_offer && asString(record.type) !== "booking_offer") {
|
|
772
|
-
const nested = parseToolCard(record.booking_offer);
|
|
773
|
-
if (nested) return nested;
|
|
774
|
-
}
|
|
775
|
-
const type = asString(record.type);
|
|
776
|
-
if (type === "booking_offer") {
|
|
777
|
-
const eventTypes = Array.isArray(record.eventTypes) ? record.eventTypes.flatMap((item) => {
|
|
778
|
-
const entry = asRecord(item);
|
|
779
|
-
const uri = asString(entry?.uri);
|
|
780
|
-
if (!entry || !isEventTypeUri(uri)) return [];
|
|
781
|
-
const duration = entry.duration;
|
|
782
|
-
const locationKind = asString(entry.locationKind);
|
|
783
|
-
const location = asString(entry.location);
|
|
784
|
-
return [
|
|
785
|
-
{
|
|
786
|
-
name: asString(entry.name) || "Meeting",
|
|
787
|
-
uri,
|
|
788
|
-
...typeof duration === "number" ? { duration } : {},
|
|
789
|
-
...locationKind ? { locationKind } : {},
|
|
790
|
-
...location ? { location } : {}
|
|
791
|
-
}
|
|
792
|
-
];
|
|
793
|
-
}) : [];
|
|
794
|
-
const slots = Array.isArray(record.slots) ? record.slots.flatMap((item) => {
|
|
795
|
-
const entry = asRecord(item);
|
|
796
|
-
const startTime = asString(entry?.startTime);
|
|
797
|
-
if (!entry || !startTime) return [];
|
|
798
|
-
const eventTypeUri = asString(entry.eventTypeUri);
|
|
799
|
-
return [
|
|
800
|
-
{
|
|
801
|
-
startTime,
|
|
802
|
-
...isEventTypeUri(eventTypeUri) ? { eventTypeUri } : {}
|
|
803
|
-
}
|
|
804
|
-
];
|
|
805
|
-
}) : [];
|
|
806
|
-
if (slots.length === 0) return null;
|
|
807
|
-
return { type: "booking_offer", eventTypes, slots };
|
|
808
|
-
}
|
|
809
|
-
if (type === "booking_confirmed") {
|
|
810
|
-
const eventUri = asString(record.eventUri);
|
|
811
|
-
if (!isEventUri(eventUri)) return null;
|
|
812
|
-
const inviteeUri = asString(record.inviteeUri);
|
|
813
|
-
const inviteeEmail = asString(record.inviteeEmail);
|
|
814
|
-
const startTime = asString(record.startTime);
|
|
815
|
-
return {
|
|
816
|
-
type: "booking_confirmed",
|
|
817
|
-
eventUri,
|
|
818
|
-
...inviteeUri ? { inviteeUri } : {},
|
|
819
|
-
...inviteeEmail ? { inviteeEmail } : {},
|
|
820
|
-
...startTime ? { startTime } : {}
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
|
-
if (type === "booking_canceled") {
|
|
824
|
-
const eventUri = asString(record.eventUri);
|
|
825
|
-
if (!isEventUri(eventUri)) return null;
|
|
826
|
-
return { type: "booking_canceled", eventUri };
|
|
827
|
-
}
|
|
828
|
-
return null;
|
|
829
|
-
}
|
|
830
|
-
function formatBookingOfferFence(offer) {
|
|
831
|
-
return [
|
|
832
|
-
"```webless-tool-card",
|
|
833
|
-
JSON.stringify({
|
|
834
|
-
type: "booking_offer",
|
|
835
|
-
eventTypes: offer.eventTypes,
|
|
836
|
-
slots: offer.slots
|
|
837
|
-
}),
|
|
838
|
-
"```"
|
|
839
|
-
].join("\n");
|
|
840
|
-
}
|
|
841
|
-
function bookingOfferFromActionOutput(output) {
|
|
842
|
-
const record = asRecord(output);
|
|
843
|
-
const data = asRecord(record?.data) ?? record;
|
|
844
|
-
const card = parseToolCard(data);
|
|
845
|
-
return card?.type === "booking_offer" ? card : null;
|
|
846
|
-
}
|
|
847
|
-
function ensureBookingOfferText(text, offer) {
|
|
848
|
-
if (!offer) return text;
|
|
849
|
-
if (extractToolCards(text).some((card) => card.type === "booking_offer")) {
|
|
850
|
-
return text;
|
|
851
|
-
}
|
|
852
|
-
const visible = stripToolCards(text).trim() || text.trim();
|
|
853
|
-
return `${visible}
|
|
854
|
-
|
|
855
|
-
${formatBookingOfferFence(offer)}`;
|
|
856
|
-
}
|
|
857
|
-
function hideToolCardFences(text) {
|
|
858
|
-
return text.replace(/```(?:webless-tool-card|json)\s*[\s\S]*?```/gi, "").replace(/```(?:webless-tool-card|json)[\s\S]*$/i, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
859
|
-
}
|
|
860
|
-
function visitorTimeZone() {
|
|
861
|
-
try {
|
|
862
|
-
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
863
|
-
} catch {
|
|
864
|
-
return "UTC";
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
function extractToolCards(text) {
|
|
868
|
-
const cards = [];
|
|
869
|
-
for (const match of text.matchAll(FENCE_PATTERN)) {
|
|
870
|
-
try {
|
|
871
|
-
const card = parseToolCard(JSON.parse(match[1] ?? ""));
|
|
872
|
-
if (card) cards.push(card);
|
|
873
|
-
} catch {
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
return cards;
|
|
877
|
-
}
|
|
878
|
-
function stripToolCards(text) {
|
|
879
|
-
return text.replace(FENCE_PATTERN, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
880
|
-
}
|
|
881
|
-
function localDateKey(date) {
|
|
882
|
-
if (Number.isNaN(date.getTime())) return "";
|
|
883
|
-
return [
|
|
884
|
-
date.getFullYear(),
|
|
885
|
-
String(date.getMonth() + 1).padStart(2, "0"),
|
|
886
|
-
String(date.getDate()).padStart(2, "0")
|
|
887
|
-
].join("-");
|
|
888
|
-
}
|
|
889
|
-
function slotDateKey(startTime) {
|
|
890
|
-
return localDateKey(new Date(startTime)) || startTime;
|
|
891
|
-
}
|
|
892
|
-
function bookingSlotsForEventType(slots, eventTypeUri) {
|
|
893
|
-
return slots.filter(
|
|
894
|
-
(slot) => !eventTypeUri || !slot.eventTypeUri || slot.eventTypeUri === eventTypeUri
|
|
895
|
-
);
|
|
896
|
-
}
|
|
897
|
-
function firstAvailableBookingMonth(slots) {
|
|
898
|
-
let earliest;
|
|
899
|
-
for (const slot of slots) {
|
|
900
|
-
const key = slotDateKey(slot.startTime);
|
|
901
|
-
if (!earliest || key < earliest) earliest = key;
|
|
902
|
-
}
|
|
903
|
-
const [year, month] = (earliest ?? slotDateKey((/* @__PURE__ */ new Date()).toISOString())).split("-").map(Number);
|
|
904
|
-
if (!year || !month) {
|
|
905
|
-
const now = /* @__PURE__ */ new Date();
|
|
906
|
-
return { year: now.getFullYear(), month: now.getMonth() };
|
|
907
|
-
}
|
|
908
|
-
return { year, month: month - 1 };
|
|
909
|
-
}
|
|
910
|
-
function formatMonthTitle(year, month) {
|
|
911
|
-
return new Intl.DateTimeFormat(void 0, {
|
|
912
|
-
month: "long",
|
|
913
|
-
year: "numeric"
|
|
914
|
-
}).format(new Date(year, month, 1));
|
|
915
|
-
}
|
|
916
|
-
function formatLongDate(startTime) {
|
|
917
|
-
const date = new Date(startTime);
|
|
918
|
-
if (Number.isNaN(date.getTime())) return startTime;
|
|
919
|
-
return new Intl.DateTimeFormat(void 0, {
|
|
920
|
-
weekday: "long",
|
|
921
|
-
month: "long",
|
|
922
|
-
day: "numeric"
|
|
923
|
-
}).format(date);
|
|
924
|
-
}
|
|
925
|
-
function weekdayLabels() {
|
|
926
|
-
return Array.from(
|
|
927
|
-
{ length: 7 },
|
|
928
|
-
(_, index) => new Intl.DateTimeFormat(void 0, { weekday: "short" }).format(
|
|
929
|
-
new Date(2026, 7, 3 + index)
|
|
930
|
-
)
|
|
931
|
-
);
|
|
932
|
-
}
|
|
933
|
-
function formatTimeChip(startTime) {
|
|
934
|
-
const date = new Date(startTime);
|
|
935
|
-
if (Number.isNaN(date.getTime())) return startTime;
|
|
936
|
-
return new Intl.DateTimeFormat(void 0, {
|
|
937
|
-
hour: "numeric",
|
|
938
|
-
minute: "2-digit"
|
|
939
|
-
}).format(date);
|
|
940
|
-
}
|
|
941
|
-
function formatSlotTimeZone(startTime) {
|
|
942
|
-
const date = new Date(startTime);
|
|
943
|
-
if (Number.isNaN(date.getTime())) return "";
|
|
944
|
-
return new Intl.DateTimeFormat(void 0, { timeZoneName: "short" }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value ?? "";
|
|
945
|
-
}
|
|
946
|
-
function formatSlotLabel(startTime) {
|
|
947
|
-
const date = new Date(startTime);
|
|
948
|
-
if (Number.isNaN(date.getTime())) return startTime;
|
|
949
|
-
return new Intl.DateTimeFormat(void 0, {
|
|
950
|
-
weekday: "short",
|
|
951
|
-
month: "short",
|
|
952
|
-
day: "numeric",
|
|
953
|
-
hour: "numeric",
|
|
954
|
-
minute: "2-digit",
|
|
955
|
-
timeZoneName: "short"
|
|
956
|
-
}).format(date);
|
|
957
|
-
}
|
|
958
|
-
function formatBookingRequest(input) {
|
|
959
|
-
return [
|
|
960
|
-
"Book this meeting now with CALENDLY_POST_INVITEE.",
|
|
961
|
-
"Do not open a Calendly URL and do not list other scheduled events.",
|
|
962
|
-
"Do not invent a location kind. Use only the location fields below.",
|
|
963
|
-
`event_type: ${input.eventTypeUri}`,
|
|
964
|
-
`start_time: ${input.startTime}`,
|
|
965
|
-
`invitee.name: ${input.inviteeName}`,
|
|
966
|
-
`invitee.email: ${input.inviteeEmail}`,
|
|
967
|
-
`invitee.timezone: ${input.timezone}`,
|
|
968
|
-
...input.locationKind ? [
|
|
969
|
-
`location.kind: ${input.locationKind}`,
|
|
970
|
-
...input.location ? [`location.location: ${input.location}`] : []
|
|
971
|
-
] : ["Do not send a location field."],
|
|
972
|
-
"After it succeeds, reply with one short confirmation and a webless-tool-card booking_confirmed block using visitor_booking."
|
|
973
|
-
].join("\n");
|
|
1808
|
+
respondTurn: (respondOptions) => session.respondTurn(
|
|
1809
|
+
respondOptions.responses,
|
|
1810
|
+
respondOptions.signal ?? new AbortController().signal,
|
|
1811
|
+
respondOptions.handlers
|
|
1812
|
+
),
|
|
1813
|
+
reset: () => session.reset(),
|
|
1814
|
+
cancelActive: () => session.cancelActive(),
|
|
1815
|
+
getActiveSessionId: () => session.getActiveSessionId()
|
|
1816
|
+
};
|
|
974
1817
|
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1818
|
+
|
|
1819
|
+
// src/runtime/errors.ts
|
|
1820
|
+
import { ClientError as ClientError3 } from "eve/client";
|
|
1821
|
+
var TRANSIENT_AGENT_ERROR_MESSAGE = "I couldn\u2019t finish that answer. Please try again.";
|
|
1822
|
+
var PREVIEW_AUTHORIZATION_ERROR_MESSAGE = "This preview could not be authorized. Open the latest preview from Webless.";
|
|
1823
|
+
function isTransientRuntimeMessage(message) {
|
|
1824
|
+
const normalized = message.trim().toLowerCase();
|
|
1825
|
+
return normalized.includes("empty response from runtime") || normalized.includes("failed to fetch") || normalized.includes("networkerror") || normalized.includes("load failed");
|
|
1826
|
+
}
|
|
1827
|
+
function isPreviewAuthorizationMessage(message) {
|
|
1828
|
+
const normalized = message.trim().toLowerCase();
|
|
1829
|
+
return normalized.includes("unpublished preview authorization") || normalized.includes("unpublished preview grant") || normalized.includes("agent studio preview grant") || normalized.includes("preview authorization");
|
|
1830
|
+
}
|
|
1831
|
+
function formatAgentError(error) {
|
|
1832
|
+
if (error instanceof ClientError3) {
|
|
1833
|
+
if (error.status === 401 && error.code === "index_required") {
|
|
1834
|
+
return "Missing indexId \u2014 pass a published index id to createAgentClient().";
|
|
1835
|
+
}
|
|
1836
|
+
if (error.status === 403 && error.code === "agent_unavailable") {
|
|
1837
|
+
return "Agent unavailable for this index (disabled or unpublished).";
|
|
1838
|
+
}
|
|
1839
|
+
if (error.status === 409 && error.code === "session_not_active") {
|
|
1840
|
+
return "Session expired \u2014 send a new message to start again.";
|
|
1841
|
+
}
|
|
1842
|
+
if (error.message && isPreviewAuthorizationMessage(error.message)) {
|
|
1843
|
+
return PREVIEW_AUTHORIZATION_ERROR_MESSAGE;
|
|
1844
|
+
}
|
|
1845
|
+
if (error.status >= 500 || error.message && isTransientRuntimeMessage(error.message)) {
|
|
1846
|
+
return TRANSIENT_AGENT_ERROR_MESSAGE;
|
|
1847
|
+
}
|
|
1848
|
+
return error.message || "This assistant is unavailable right now.";
|
|
1849
|
+
}
|
|
1850
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
1851
|
+
return "";
|
|
1852
|
+
}
|
|
1853
|
+
if (error instanceof Error) {
|
|
1854
|
+
if (isPreviewAuthorizationMessage(error.message)) {
|
|
1855
|
+
return PREVIEW_AUTHORIZATION_ERROR_MESSAGE;
|
|
1856
|
+
}
|
|
1857
|
+
return isTransientRuntimeMessage(error.message) ? TRANSIENT_AGENT_ERROR_MESSAGE : error.message;
|
|
1858
|
+
}
|
|
1859
|
+
return TRANSIENT_AGENT_ERROR_MESSAGE;
|
|
985
1860
|
}
|
|
986
1861
|
|
|
987
1862
|
// src/react/persisted-conversation.ts
|
|
988
|
-
var CONVERSATION_VERSION =
|
|
1863
|
+
var CONVERSATION_VERSION = 3;
|
|
989
1864
|
function conversationKey(storageKeyPrefix, visitorSessionId) {
|
|
990
1865
|
return `${storageKeyPrefix}:conversation:${visitorSessionId}`;
|
|
991
1866
|
}
|
|
@@ -1025,30 +1900,171 @@ function parseToolStep(value) {
|
|
|
1025
1900
|
...typeof record.detail === "string" && record.detail ? { detail: record.detail } : {}
|
|
1026
1901
|
};
|
|
1027
1902
|
}
|
|
1903
|
+
function parseInputOption(value) {
|
|
1904
|
+
if (typeof value !== "object" || value === null) return null;
|
|
1905
|
+
const record = value;
|
|
1906
|
+
if (typeof record.id !== "string" || typeof record.label !== "string") {
|
|
1907
|
+
return null;
|
|
1908
|
+
}
|
|
1909
|
+
return {
|
|
1910
|
+
id: record.id,
|
|
1911
|
+
label: record.label,
|
|
1912
|
+
...typeof record.description === "string" ? { description: record.description } : {},
|
|
1913
|
+
...record.style === "default" || record.style === "primary" || record.style === "danger" ? { style: record.style } : {}
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
function parseInputRequest(value) {
|
|
1917
|
+
if (typeof value !== "object" || value === null) return null;
|
|
1918
|
+
const record = value;
|
|
1919
|
+
if (typeof record.requestId !== "string" || record.kind !== "question" && record.kind !== "session-limit" && record.kind !== "tool-approval" || typeof record.prompt !== "string") {
|
|
1920
|
+
return null;
|
|
1921
|
+
}
|
|
1922
|
+
const action = record.action;
|
|
1923
|
+
if (typeof action !== "object" || action === null) return null;
|
|
1924
|
+
const actionRecord = action;
|
|
1925
|
+
if (typeof actionRecord.callId !== "string" || actionRecord.kind !== "tool-call" || typeof actionRecord.toolName !== "string") {
|
|
1926
|
+
return null;
|
|
1927
|
+
}
|
|
1928
|
+
const options = Array.isArray(record.options) ? record.options.map(parseInputOption) : void 0;
|
|
1929
|
+
if (Array.isArray(record.options) && options?.some((option) => option === null)) {
|
|
1930
|
+
return null;
|
|
1931
|
+
}
|
|
1932
|
+
const ui = record.ui ? parseAgentToolUiSurface(record.ui) : void 0;
|
|
1933
|
+
if (record.ui && !ui) return null;
|
|
1934
|
+
return {
|
|
1935
|
+
requestId: record.requestId,
|
|
1936
|
+
kind: record.kind,
|
|
1937
|
+
prompt: record.prompt,
|
|
1938
|
+
action: {
|
|
1939
|
+
callId: actionRecord.callId,
|
|
1940
|
+
kind: "tool-call",
|
|
1941
|
+
toolName: actionRecord.toolName
|
|
1942
|
+
},
|
|
1943
|
+
...record.display === "confirmation" || record.display === "select" || record.display === "text" ? { display: record.display } : {},
|
|
1944
|
+
...record.allowFreeform === true ? { allowFreeform: true } : {},
|
|
1945
|
+
...options && options.length > 0 ? {
|
|
1946
|
+
options: options.filter(
|
|
1947
|
+
(option) => option !== null
|
|
1948
|
+
)
|
|
1949
|
+
} : {},
|
|
1950
|
+
...ui ? { ui } : {}
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
function parseToolResult(value) {
|
|
1954
|
+
if (typeof value !== "object" || value === null) return null;
|
|
1955
|
+
const record = value;
|
|
1956
|
+
if (typeof record.id !== "string" || typeof record.toolName !== "string" || record.status !== "completed" && record.status !== "failed" && record.status !== "rejected") {
|
|
1957
|
+
return null;
|
|
1958
|
+
}
|
|
1959
|
+
if (record.kind === "input") {
|
|
1960
|
+
const surface = parseAgentToolUiSurface(
|
|
1961
|
+
record.surface
|
|
1962
|
+
);
|
|
1963
|
+
return surface ? {
|
|
1964
|
+
id: record.id,
|
|
1965
|
+
toolName: record.toolName,
|
|
1966
|
+
status: record.status,
|
|
1967
|
+
kind: "input",
|
|
1968
|
+
surface
|
|
1969
|
+
} : null;
|
|
1970
|
+
}
|
|
1971
|
+
if (record.kind === "entity" && typeof record.title === "string") {
|
|
1972
|
+
return {
|
|
1973
|
+
id: record.id,
|
|
1974
|
+
toolName: record.toolName,
|
|
1975
|
+
status: record.status,
|
|
1976
|
+
kind: "entity",
|
|
1977
|
+
title: record.title,
|
|
1978
|
+
...typeof record.description === "string" ? { description: record.description } : {}
|
|
1979
|
+
};
|
|
1980
|
+
}
|
|
1981
|
+
if (record.kind === "collection" && typeof record.title === "string" && Array.isArray(record.items)) {
|
|
1982
|
+
return {
|
|
1983
|
+
id: record.id,
|
|
1984
|
+
toolName: record.toolName,
|
|
1985
|
+
status: record.status,
|
|
1986
|
+
kind: "collection",
|
|
1987
|
+
title: record.title,
|
|
1988
|
+
items: record.items.flatMap((item) => {
|
|
1989
|
+
if (typeof item !== "object" || item === null) return [];
|
|
1990
|
+
const entry = item;
|
|
1991
|
+
if (typeof entry.title !== "string") return [];
|
|
1992
|
+
return [
|
|
1993
|
+
{
|
|
1994
|
+
title: entry.title,
|
|
1995
|
+
...typeof entry.description === "string" ? { description: entry.description } : {},
|
|
1996
|
+
...typeof entry.href === "string" ? { href: entry.href } : {}
|
|
1997
|
+
}
|
|
1998
|
+
];
|
|
1999
|
+
})
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
if (record.kind === "signature" && typeof record.title === "string") {
|
|
2003
|
+
return {
|
|
2004
|
+
id: record.id,
|
|
2005
|
+
toolName: record.toolName,
|
|
2006
|
+
status: record.status,
|
|
2007
|
+
kind: "signature",
|
|
2008
|
+
title: record.title,
|
|
2009
|
+
...typeof record.description === "string" ? { description: record.description } : {},
|
|
2010
|
+
...typeof record.statusLabel === "string" ? { statusLabel: record.statusLabel } : {}
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
if (record.kind !== "summary" || typeof record.title !== "string")
|
|
2014
|
+
return null;
|
|
2015
|
+
return {
|
|
2016
|
+
id: record.id,
|
|
2017
|
+
toolName: record.toolName,
|
|
2018
|
+
status: record.status,
|
|
2019
|
+
kind: "summary",
|
|
2020
|
+
title: record.title,
|
|
2021
|
+
...typeof record.description === "string" ? { description: record.description } : {}
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
1028
2024
|
function visitorTurnText(message) {
|
|
1029
2025
|
return message.role === "visitor" && message.runtimeText ? message.runtimeText : message.text;
|
|
1030
2026
|
}
|
|
1031
2027
|
function loadPersistedAgentConversation(storageKeyPrefix, visitorSessionId) {
|
|
1032
2028
|
if (typeof sessionStorage === "undefined") return null;
|
|
1033
|
-
const raw = sessionStorage.getItem(
|
|
2029
|
+
const raw = sessionStorage.getItem(
|
|
2030
|
+
conversationKey(storageKeyPrefix, visitorSessionId)
|
|
2031
|
+
);
|
|
1034
2032
|
if (!raw) return null;
|
|
1035
2033
|
try {
|
|
1036
2034
|
const value = JSON.parse(raw);
|
|
1037
2035
|
if (typeof value !== "object" || value === null) return null;
|
|
1038
2036
|
const record = value;
|
|
1039
|
-
if (record.version !== 1 && record.version !== CONVERSATION_VERSION || !Array.isArray(record.messages) || typeof record.pending !== "boolean" || typeof record.streamingText !== "string" || record.version === CONVERSATION_VERSION && !Array.isArray(record.toolSteps)) {
|
|
2037
|
+
if (record.version !== 1 && record.version !== 2 && record.version !== CONVERSATION_VERSION || !Array.isArray(record.messages) || typeof record.pending !== "boolean" || typeof record.streamingText !== "string" || record.version === CONVERSATION_VERSION && !Array.isArray(record.toolSteps)) {
|
|
1040
2038
|
return null;
|
|
1041
2039
|
}
|
|
1042
2040
|
const messages = record.messages.map(parseMessage);
|
|
1043
2041
|
if (messages.some((message) => message === null)) return null;
|
|
1044
|
-
const storedToolSteps = record.version === CONVERSATION_VERSION && Array.isArray(record.toolSteps) ? record.toolSteps : [];
|
|
2042
|
+
const storedToolSteps = (record.version === 2 || record.version === CONVERSATION_VERSION) && Array.isArray(record.toolSteps) ? record.toolSteps : [];
|
|
1045
2043
|
const toolSteps = storedToolSteps.map(parseToolStep);
|
|
1046
2044
|
if (toolSteps.some((step) => step === null)) return null;
|
|
2045
|
+
const storedToolResults = record.version === CONVERSATION_VERSION && Array.isArray(record.toolResults) ? record.toolResults : [];
|
|
2046
|
+
const toolResults = storedToolResults.map(parseToolResult);
|
|
2047
|
+
if (toolResults.some((result) => result === null)) return null;
|
|
2048
|
+
const storedPendingInputs = Array.isArray(record.pendingInputs) ? record.pendingInputs : [];
|
|
2049
|
+
const pendingInputs = storedPendingInputs.map(parseInputRequest);
|
|
2050
|
+
if (pendingInputs.some((request) => request === null)) return null;
|
|
1047
2051
|
return {
|
|
1048
|
-
messages: messages.filter(
|
|
2052
|
+
messages: messages.filter(
|
|
2053
|
+
(message) => message !== null
|
|
2054
|
+
),
|
|
1049
2055
|
pending: record.pending,
|
|
1050
2056
|
streamingText: record.streamingText,
|
|
1051
|
-
toolSteps: toolSteps.filter((step) => step !== null)
|
|
2057
|
+
toolSteps: toolSteps.filter((step) => step !== null),
|
|
2058
|
+
...Array.isArray(record.toolResults) ? {
|
|
2059
|
+
toolResults: toolResults.filter(
|
|
2060
|
+
(result) => result !== null
|
|
2061
|
+
)
|
|
2062
|
+
} : {},
|
|
2063
|
+
...pendingInputs.length > 0 ? {
|
|
2064
|
+
pendingInputs: pendingInputs.filter(
|
|
2065
|
+
(request) => request !== null
|
|
2066
|
+
)
|
|
2067
|
+
} : {}
|
|
1052
2068
|
};
|
|
1053
2069
|
} catch {
|
|
1054
2070
|
return null;
|
|
@@ -1063,7 +2079,9 @@ function savePersistedAgentConversation(storageKeyPrefix, visitorSessionId, conv
|
|
|
1063
2079
|
}
|
|
1064
2080
|
function clearPersistedAgentConversation(storageKeyPrefix, visitorSessionId) {
|
|
1065
2081
|
if (typeof sessionStorage === "undefined") return;
|
|
1066
|
-
sessionStorage.removeItem(
|
|
2082
|
+
sessionStorage.removeItem(
|
|
2083
|
+
conversationKey(storageKeyPrefix, visitorSessionId)
|
|
2084
|
+
);
|
|
1067
2085
|
clearPendingWidgetBooking(storageKeyPrefix, visitorSessionId);
|
|
1068
2086
|
}
|
|
1069
2087
|
function pendingBookingKey(storageKeyPrefix, visitorSessionId) {
|
|
@@ -1099,7 +2117,76 @@ function savePendingWidgetBooking(storageKeyPrefix, visitorSessionId, booking) {
|
|
|
1099
2117
|
}
|
|
1100
2118
|
function clearPendingWidgetBooking(storageKeyPrefix, visitorSessionId) {
|
|
1101
2119
|
if (typeof sessionStorage === "undefined") return;
|
|
1102
|
-
sessionStorage.removeItem(
|
|
2120
|
+
sessionStorage.removeItem(
|
|
2121
|
+
pendingBookingKey(storageKeyPrefix, visitorSessionId)
|
|
2122
|
+
);
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
// src/react/lib/visitor-input.ts
|
|
2126
|
+
function shouldRenderVisitorInputCard(request) {
|
|
2127
|
+
if (request.kind === "tool-approval" || request.kind === "session-limit") {
|
|
2128
|
+
return true;
|
|
2129
|
+
}
|
|
2130
|
+
if (request.kind === "question") {
|
|
2131
|
+
return (request.options?.length ?? 0) > 0;
|
|
2132
|
+
}
|
|
2133
|
+
return false;
|
|
2134
|
+
}
|
|
2135
|
+
function isChatCollectibleInputRequest(request) {
|
|
2136
|
+
return request.kind === "question" && (request.options?.length ?? 0) === 0;
|
|
2137
|
+
}
|
|
2138
|
+
function appendChatCollectiblePrompts(messages, requests) {
|
|
2139
|
+
const next = [...messages];
|
|
2140
|
+
for (const request of requests.filter(isChatCollectibleInputRequest)) {
|
|
2141
|
+
const prompt = request.prompt.trim();
|
|
2142
|
+
if (!prompt) continue;
|
|
2143
|
+
const last = next.at(-1);
|
|
2144
|
+
if (last?.role === "agent" && last.text.trim() === prompt) continue;
|
|
2145
|
+
next.push({
|
|
2146
|
+
id: `agent-input-${request.requestId}`,
|
|
2147
|
+
role: "agent",
|
|
2148
|
+
text: prompt,
|
|
2149
|
+
createdAt: Date.now()
|
|
2150
|
+
});
|
|
2151
|
+
}
|
|
2152
|
+
return next;
|
|
2153
|
+
}
|
|
2154
|
+
function chatInputResponseForText(requests, text) {
|
|
2155
|
+
const trimmed = text.trim();
|
|
2156
|
+
if (!trimmed) return null;
|
|
2157
|
+
const pending = requests.find(isChatCollectibleInputRequest);
|
|
2158
|
+
if (!pending) return null;
|
|
2159
|
+
return { requestId: pending.requestId, text: trimmed };
|
|
2160
|
+
}
|
|
2161
|
+
function normalizeAssistantDedupeKey(text) {
|
|
2162
|
+
return text.trim().replace(/\s+/g, " ").toLowerCase();
|
|
2163
|
+
}
|
|
2164
|
+
function isNearDuplicateAssistantText(left, right) {
|
|
2165
|
+
const a = normalizeAssistantDedupeKey(left);
|
|
2166
|
+
const b = normalizeAssistantDedupeKey(right);
|
|
2167
|
+
if (!a || !b) return false;
|
|
2168
|
+
if (a === b) return true;
|
|
2169
|
+
const shorter = a.length <= b.length ? a : b;
|
|
2170
|
+
const longer = a.length <= b.length ? b : a;
|
|
2171
|
+
if (shorter.length < 40) return false;
|
|
2172
|
+
return longer.startsWith(
|
|
2173
|
+
shorter.slice(0, Math.floor(shorter.length * 0.85))
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
function appendAgentTurnMessage(messages, displayText) {
|
|
2177
|
+
const trimmed = displayText.trim();
|
|
2178
|
+
if (!trimmed) return [...messages];
|
|
2179
|
+
const agentMessage = {
|
|
2180
|
+
id: `agent-${Date.now()}`,
|
|
2181
|
+
role: "agent",
|
|
2182
|
+
text: trimmed,
|
|
2183
|
+
createdAt: Date.now()
|
|
2184
|
+
};
|
|
2185
|
+
const last = messages.at(-1);
|
|
2186
|
+
if (last?.role === "agent" && (last.id.startsWith("agent-input-") || isNearDuplicateAssistantText(last.text, trimmed))) {
|
|
2187
|
+
return [...messages.slice(0, -1), agentMessage];
|
|
2188
|
+
}
|
|
2189
|
+
return [...messages, agentMessage];
|
|
1103
2190
|
}
|
|
1104
2191
|
|
|
1105
2192
|
// src/react/hooks/useAgentChat.ts
|
|
@@ -1120,17 +2207,23 @@ function createInitialState(greeting = DEFAULT_GREETING) {
|
|
|
1120
2207
|
followUps: [],
|
|
1121
2208
|
streamingText: "",
|
|
1122
2209
|
pendingOffer: null,
|
|
2210
|
+
pendingInputs: [],
|
|
2211
|
+
toolResults: [],
|
|
1123
2212
|
error: null
|
|
1124
2213
|
};
|
|
1125
2214
|
}
|
|
1126
2215
|
function stateFromConversation(conversation, initialState) {
|
|
1127
2216
|
if (!conversation || conversation.messages.length === 0) return initialState;
|
|
2217
|
+
const pendingInputs = conversation.pendingInputs ?? [];
|
|
2218
|
+
const hasWaitingInput = pendingInputs.length > 0;
|
|
1128
2219
|
return {
|
|
1129
2220
|
...initialState,
|
|
1130
2221
|
messages: conversation.messages,
|
|
1131
|
-
phase: conversation.pending ? conversation.streamingText ? "streaming" : "thinking" : "complete",
|
|
2222
|
+
phase: hasWaitingInput ? "waiting-input" : conversation.pending ? conversation.streamingText ? "streaming" : "thinking" : "complete",
|
|
1132
2223
|
streamingText: conversation.streamingText,
|
|
1133
|
-
toolSteps: conversation.toolSteps
|
|
2224
|
+
toolSteps: conversation.toolSteps,
|
|
2225
|
+
toolResults: conversation.toolResults,
|
|
2226
|
+
...hasWaitingInput ? { pendingInputs } : {}
|
|
1134
2227
|
};
|
|
1135
2228
|
}
|
|
1136
2229
|
function upsertToolStep(steps, item) {
|
|
@@ -1145,6 +2238,24 @@ function upsertToolStep(steps, item) {
|
|
|
1145
2238
|
if (index < 0) return [...steps, next];
|
|
1146
2239
|
return steps.map((step, stepIndex) => stepIndex === index ? next : step);
|
|
1147
2240
|
}
|
|
2241
|
+
function applyBookingOffer(prev, card) {
|
|
2242
|
+
const pendingOffer = preferBookingOffer(prev.pendingOffer, card);
|
|
2243
|
+
const last = prev.messages.at(-1);
|
|
2244
|
+
if (last?.role === "agent" && prev.phase === "complete") {
|
|
2245
|
+
return {
|
|
2246
|
+
...prev,
|
|
2247
|
+
pendingOffer,
|
|
2248
|
+
messages: [
|
|
2249
|
+
...prev.messages.slice(0, -1),
|
|
2250
|
+
{
|
|
2251
|
+
...last,
|
|
2252
|
+
text: ensureBookingOfferText(last.text, pendingOffer)
|
|
2253
|
+
}
|
|
2254
|
+
]
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
2257
|
+
return { ...prev, pendingOffer };
|
|
2258
|
+
}
|
|
1148
2259
|
function completeActivePlanning(steps) {
|
|
1149
2260
|
return steps.map(
|
|
1150
2261
|
(step) => step.kind === "planning" && step.state === "active" ? {
|
|
@@ -1154,6 +2265,9 @@ function completeActivePlanning(steps) {
|
|
|
1154
2265
|
} : step
|
|
1155
2266
|
);
|
|
1156
2267
|
}
|
|
2268
|
+
function isJsonRecord(value) {
|
|
2269
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2270
|
+
}
|
|
1157
2271
|
function useAgentChat({
|
|
1158
2272
|
customerId,
|
|
1159
2273
|
getUnpublishedPreviewGrant,
|
|
@@ -1163,7 +2277,8 @@ function useAgentChat({
|
|
|
1163
2277
|
runtimeOrigin,
|
|
1164
2278
|
visitorSessionId,
|
|
1165
2279
|
storageKeyPrefix,
|
|
1166
|
-
greeting
|
|
2280
|
+
greeting,
|
|
2281
|
+
toolResultRegistry
|
|
1167
2282
|
}) {
|
|
1168
2283
|
const initialState = useMemo(
|
|
1169
2284
|
() => createInitialState(greeting?.trim() || DEFAULT_GREETING),
|
|
@@ -1171,6 +2286,8 @@ function useAgentChat({
|
|
|
1171
2286
|
);
|
|
1172
2287
|
const previewGrantProviderRef = useRef(getUnpublishedPreviewGrant);
|
|
1173
2288
|
previewGrantProviderRef.current = getUnpublishedPreviewGrant;
|
|
2289
|
+
const toolResultRegistryRef = useRef(toolResultRegistry);
|
|
2290
|
+
toolResultRegistryRef.current = toolResultRegistry;
|
|
1174
2291
|
const resolveUnpublishedPreviewGrant = () => {
|
|
1175
2292
|
const provider = previewGrantProviderRef.current;
|
|
1176
2293
|
if (!provider) {
|
|
@@ -1265,14 +2382,18 @@ function useAgentChat({
|
|
|
1265
2382
|
messages: state.messages,
|
|
1266
2383
|
pending: isAgentBusy(state.phase),
|
|
1267
2384
|
streamingText: state.streamingText,
|
|
1268
|
-
toolSteps: state.toolSteps
|
|
2385
|
+
toolSteps: state.toolSteps,
|
|
2386
|
+
toolResults: state.toolResults ?? [],
|
|
2387
|
+
...state.pendingInputs && state.pendingInputs.length > 0 ? { pendingInputs: state.pendingInputs } : {}
|
|
1269
2388
|
});
|
|
1270
2389
|
}, [
|
|
1271
2390
|
resolvedStorageKeyPrefix,
|
|
1272
2391
|
state.messages,
|
|
2392
|
+
state.pendingInputs,
|
|
1273
2393
|
state.phase,
|
|
1274
2394
|
state.streamingText,
|
|
1275
2395
|
state.toolSteps,
|
|
2396
|
+
state.toolResults,
|
|
1276
2397
|
visitorId
|
|
1277
2398
|
]);
|
|
1278
2399
|
const reset = useCallback(() => {
|
|
@@ -1285,13 +2406,20 @@ function useAgentChat({
|
|
|
1285
2406
|
}, [initialState, resolvedStorageKeyPrefix, visitorId]);
|
|
1286
2407
|
const runTurn = useCallback(
|
|
1287
2408
|
async (input) => {
|
|
1288
|
-
const {
|
|
2409
|
+
const {
|
|
2410
|
+
controller,
|
|
2411
|
+
initialText = "",
|
|
2412
|
+
responses,
|
|
2413
|
+
resume,
|
|
2414
|
+
visitorText
|
|
2415
|
+
} = input;
|
|
1289
2416
|
const { signal } = controller;
|
|
1290
2417
|
const isActiveRun = () => runRef.current === controller && !signal.aborted;
|
|
1291
2418
|
try {
|
|
1292
2419
|
let streamStarted = Boolean(initialText);
|
|
1293
2420
|
let streamed = initialText;
|
|
1294
2421
|
const capturedOffers = [];
|
|
2422
|
+
let capturedInputCount = 0;
|
|
1295
2423
|
const handlers = {
|
|
1296
2424
|
onWork: (item) => {
|
|
1297
2425
|
if (!isActiveRun()) return;
|
|
@@ -1301,11 +2429,62 @@ function useAgentChat({
|
|
|
1301
2429
|
toolSteps: upsertToolStep(prev.toolSteps, item)
|
|
1302
2430
|
}));
|
|
1303
2431
|
},
|
|
1304
|
-
|
|
1305
|
-
const
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
2432
|
+
onToolResult: (result) => {
|
|
2433
|
+
const presentation = presentVisitorToolResult(
|
|
2434
|
+
result,
|
|
2435
|
+
toolResultRegistryRef.current
|
|
2436
|
+
);
|
|
2437
|
+
if (presentation.kind === "booking") {
|
|
2438
|
+
const card = presentation.card;
|
|
2439
|
+
if (card.type === "booking_offer") {
|
|
2440
|
+
capturedOffers.push(card);
|
|
2441
|
+
setState((prev) => applyBookingOffer(prev, card));
|
|
2442
|
+
return;
|
|
2443
|
+
}
|
|
2444
|
+
if (!isActiveRun()) return;
|
|
2445
|
+
if (card.type === "booking_confirmed") {
|
|
2446
|
+
pendingBookingRef.current = card;
|
|
2447
|
+
savePendingWidgetBooking(
|
|
2448
|
+
resolvedStorageKeyPrefix,
|
|
2449
|
+
visitorId,
|
|
2450
|
+
card
|
|
2451
|
+
);
|
|
2452
|
+
} else if (card.type === "booking_canceled" && pendingBookingRef.current?.eventUri === card.eventUri) {
|
|
2453
|
+
pendingBookingRef.current = null;
|
|
2454
|
+
clearPendingWidgetBooking(resolvedStorageKeyPrefix, visitorId);
|
|
2455
|
+
}
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
if (!isActiveRun()) return;
|
|
2459
|
+
if (presentation.kind === "hidden") return;
|
|
2460
|
+
if (presentation.kind === "input") return;
|
|
2461
|
+
setState((prev) => ({
|
|
2462
|
+
...prev,
|
|
2463
|
+
toolResults: [
|
|
2464
|
+
...(prev.toolResults ?? []).filter(
|
|
2465
|
+
(item) => item.id !== presentation.id
|
|
2466
|
+
),
|
|
2467
|
+
presentation
|
|
2468
|
+
]
|
|
2469
|
+
}));
|
|
2470
|
+
},
|
|
2471
|
+
onInputRequest: (requests) => {
|
|
2472
|
+
if (!isActiveRun()) return;
|
|
2473
|
+
capturedInputCount += requests.length;
|
|
2474
|
+
const cardRequests = requests.filter(shouldRenderVisitorInputCard);
|
|
2475
|
+
const chatRequests = requests.filter(isChatCollectibleInputRequest);
|
|
2476
|
+
setState((prev) => ({
|
|
2477
|
+
...prev,
|
|
2478
|
+
phase: cardRequests.length > 0 ? "waiting-input" : "complete",
|
|
2479
|
+
pendingInputs: [...requests],
|
|
2480
|
+
...chatRequests.length > 0 ? {
|
|
2481
|
+
messages: appendChatCollectiblePrompts(
|
|
2482
|
+
prev.messages,
|
|
2483
|
+
chatRequests
|
|
2484
|
+
),
|
|
2485
|
+
toolSteps: completeActivePlanning(prev.toolSteps)
|
|
2486
|
+
} : {}
|
|
2487
|
+
}));
|
|
1309
2488
|
},
|
|
1310
2489
|
onDelta: (delta) => {
|
|
1311
2490
|
if (!isActiveRun()) return;
|
|
@@ -1331,7 +2510,11 @@ function useAgentChat({
|
|
|
1331
2510
|
streamStarted = true;
|
|
1332
2511
|
}
|
|
1333
2512
|
};
|
|
1334
|
-
let finalText =
|
|
2513
|
+
let finalText = responses ? await clientRef.current.respondTurn({
|
|
2514
|
+
handlers,
|
|
2515
|
+
responses,
|
|
2516
|
+
signal
|
|
2517
|
+
}) : resume ? await clientRef.current.resumeTurn({
|
|
1335
2518
|
handlers,
|
|
1336
2519
|
initialText,
|
|
1337
2520
|
message: visitorText,
|
|
@@ -1343,17 +2526,15 @@ function useAgentChat({
|
|
|
1343
2526
|
signal
|
|
1344
2527
|
});
|
|
1345
2528
|
}
|
|
1346
|
-
if (!isActiveRun() || finalText === null) return;
|
|
2529
|
+
if (!isActiveRun() || finalText === null) return null;
|
|
2530
|
+
if (!finalText.trim() && capturedInputCount > 0) {
|
|
2531
|
+
runRef.current = null;
|
|
2532
|
+
return null;
|
|
2533
|
+
}
|
|
1347
2534
|
const displayText = ensureBookingOfferText(
|
|
1348
2535
|
finalText,
|
|
1349
2536
|
capturedOffers.at(-1) ?? null
|
|
1350
2537
|
);
|
|
1351
|
-
const agentMessage = {
|
|
1352
|
-
id: `agent-${Date.now()}`,
|
|
1353
|
-
role: "agent",
|
|
1354
|
-
text: displayText,
|
|
1355
|
-
createdAt: Date.now()
|
|
1356
|
-
};
|
|
1357
2538
|
const parsedCards = extractToolCards(displayText);
|
|
1358
2539
|
for (const card of parsedCards) {
|
|
1359
2540
|
if (card.type === "booking_confirmed") {
|
|
@@ -1367,21 +2548,25 @@ function useAgentChat({
|
|
|
1367
2548
|
}
|
|
1368
2549
|
setState((prev) => ({
|
|
1369
2550
|
...prev,
|
|
1370
|
-
phase: "complete",
|
|
1371
|
-
messages:
|
|
2551
|
+
phase: (prev.pendingInputs ?? []).some(shouldRenderVisitorInputCard) ? "waiting-input" : "complete",
|
|
2552
|
+
messages: appendAgentTurnMessage(prev.messages, displayText),
|
|
1372
2553
|
toolSteps: completeActivePlanning(prev.toolSteps),
|
|
1373
2554
|
streamingText: "",
|
|
1374
|
-
pendingOffer:
|
|
2555
|
+
pendingOffer: capturedOffers.at(-1) ?? prev.pendingOffer,
|
|
2556
|
+
pendingInputs: (prev.pendingInputs ?? []).filter(
|
|
2557
|
+
isChatCollectibleInputRequest
|
|
2558
|
+
),
|
|
1375
2559
|
followUps: [],
|
|
1376
2560
|
journey: null
|
|
1377
2561
|
}));
|
|
1378
2562
|
runRef.current = null;
|
|
2563
|
+
return displayText;
|
|
1379
2564
|
} catch (error) {
|
|
1380
2565
|
if (error instanceof DOMException && error.name === "AbortError")
|
|
1381
|
-
return;
|
|
1382
|
-
if (!isActiveRun()) return;
|
|
2566
|
+
return null;
|
|
2567
|
+
if (!isActiveRun()) return null;
|
|
1383
2568
|
const message = formatAgentError(error);
|
|
1384
|
-
if (!message) return;
|
|
2569
|
+
if (!message) return null;
|
|
1385
2570
|
setState((prev) => ({
|
|
1386
2571
|
...prev,
|
|
1387
2572
|
phase: "error",
|
|
@@ -1397,6 +2582,7 @@ function useAgentChat({
|
|
|
1397
2582
|
error: message
|
|
1398
2583
|
}));
|
|
1399
2584
|
runRef.current = null;
|
|
2585
|
+
return null;
|
|
1400
2586
|
}
|
|
1401
2587
|
},
|
|
1402
2588
|
[resolvedStorageKeyPrefix, visitorId]
|
|
@@ -1424,6 +2610,49 @@ function useAgentChat({
|
|
|
1424
2610
|
);
|
|
1425
2611
|
const submit = useCallback(
|
|
1426
2612
|
async (visitorText, options) => {
|
|
2613
|
+
const trimmed = visitorText.trim();
|
|
2614
|
+
if (!trimmed) return null;
|
|
2615
|
+
const chatResponse = chatInputResponseForText(
|
|
2616
|
+
state.pendingInputs ?? [],
|
|
2617
|
+
trimmed
|
|
2618
|
+
);
|
|
2619
|
+
if (chatResponse) {
|
|
2620
|
+
const visitorMessage2 = {
|
|
2621
|
+
id: `visitor-${Date.now()}`,
|
|
2622
|
+
role: "visitor",
|
|
2623
|
+
text: trimmed,
|
|
2624
|
+
createdAt: Date.now()
|
|
2625
|
+
};
|
|
2626
|
+
if (runRef.current) {
|
|
2627
|
+
runRef.current.abort();
|
|
2628
|
+
clientRef.current.cancelActive();
|
|
2629
|
+
}
|
|
2630
|
+
const controller2 = new AbortController();
|
|
2631
|
+
runRef.current = controller2;
|
|
2632
|
+
setState((prev) => ({
|
|
2633
|
+
...prev,
|
|
2634
|
+
phase: "running-tools",
|
|
2635
|
+
messages: [...prev.messages, visitorMessage2],
|
|
2636
|
+
pendingInputs: (prev.pendingInputs ?? []).filter(
|
|
2637
|
+
(request) => request.requestId !== chatResponse.requestId
|
|
2638
|
+
),
|
|
2639
|
+
toolSteps: [
|
|
2640
|
+
{
|
|
2641
|
+
id: "planning",
|
|
2642
|
+
kind: "planning",
|
|
2643
|
+
label: "Understanding your question",
|
|
2644
|
+
state: "active"
|
|
2645
|
+
}
|
|
2646
|
+
],
|
|
2647
|
+
error: null
|
|
2648
|
+
}));
|
|
2649
|
+
return await runTurn({
|
|
2650
|
+
controller: controller2,
|
|
2651
|
+
responses: [chatResponse],
|
|
2652
|
+
resume: false,
|
|
2653
|
+
visitorText: ""
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
1427
2656
|
if (runRef.current) {
|
|
1428
2657
|
runRef.current.abort();
|
|
1429
2658
|
clientRef.current.cancelActive();
|
|
@@ -1458,11 +2687,17 @@ ${outgoing}` : outgoing;
|
|
|
1458
2687
|
followUps: [],
|
|
1459
2688
|
streamingText: "",
|
|
1460
2689
|
pendingOffer: null,
|
|
2690
|
+
pendingInputs: [],
|
|
2691
|
+
toolResults: [],
|
|
1461
2692
|
error: null
|
|
1462
2693
|
}));
|
|
1463
|
-
await runTurn({
|
|
2694
|
+
return await runTurn({
|
|
2695
|
+
controller,
|
|
2696
|
+
resume: false,
|
|
2697
|
+
visitorText: runtimeText
|
|
2698
|
+
});
|
|
1464
2699
|
},
|
|
1465
|
-
[runTurn]
|
|
2700
|
+
[runTurn, state.pendingInputs]
|
|
1466
2701
|
);
|
|
1467
2702
|
const retry = useCallback(async () => {
|
|
1468
2703
|
const visitorMessage = [...state.messages].reverse().find((message) => message.role === "visitor");
|
|
@@ -1488,6 +2723,8 @@ ${outgoing}` : outgoing;
|
|
|
1488
2723
|
followUps: [],
|
|
1489
2724
|
streamingText: "",
|
|
1490
2725
|
pendingOffer: null,
|
|
2726
|
+
pendingInputs: [],
|
|
2727
|
+
toolResults: [],
|
|
1491
2728
|
error: null
|
|
1492
2729
|
}));
|
|
1493
2730
|
await runTurn({
|
|
@@ -1496,6 +2733,50 @@ ${outgoing}` : outgoing;
|
|
|
1496
2733
|
visitorText: visitorTurnText(visitorMessage)
|
|
1497
2734
|
});
|
|
1498
2735
|
}, [runTurn, state.messages]);
|
|
2736
|
+
const respondToToolInput = useCallback(
|
|
2737
|
+
async (surface, values) => {
|
|
2738
|
+
const details = JSON.stringify(values);
|
|
2739
|
+
await submit(`${surface.title} details provided`, {
|
|
2740
|
+
runtimeText: [
|
|
2741
|
+
`Structured input provided for ${surface.toolSlug}.`,
|
|
2742
|
+
surface.operationId ? `Operation: ${surface.operationId}.` : "",
|
|
2743
|
+
`Use these exact values and retry the action: ${details}`,
|
|
2744
|
+
"Do not invent or alter any values."
|
|
2745
|
+
].filter(Boolean).join("\n")
|
|
2746
|
+
});
|
|
2747
|
+
},
|
|
2748
|
+
[submit]
|
|
2749
|
+
);
|
|
2750
|
+
const respondToInput = useCallback(
|
|
2751
|
+
async (response) => {
|
|
2752
|
+
if (runRef.current) return;
|
|
2753
|
+
const pending = state.pendingInputs?.find(
|
|
2754
|
+
(request) => request.requestId === response.requestId
|
|
2755
|
+
);
|
|
2756
|
+
if (!pending) return;
|
|
2757
|
+
if (pending.ui && isJsonRecord(response.value)) {
|
|
2758
|
+
await respondToToolInput(pending.ui, response.value);
|
|
2759
|
+
return;
|
|
2760
|
+
}
|
|
2761
|
+
const controller = new AbortController();
|
|
2762
|
+
runRef.current = controller;
|
|
2763
|
+
setState((prev) => ({
|
|
2764
|
+
...prev,
|
|
2765
|
+
phase: "running-tools",
|
|
2766
|
+
pendingInputs: (prev.pendingInputs ?? []).filter(
|
|
2767
|
+
(request) => request.requestId !== response.requestId
|
|
2768
|
+
),
|
|
2769
|
+
error: null
|
|
2770
|
+
}));
|
|
2771
|
+
await runTurn({
|
|
2772
|
+
controller,
|
|
2773
|
+
responses: [response],
|
|
2774
|
+
resume: false,
|
|
2775
|
+
visitorText: ""
|
|
2776
|
+
});
|
|
2777
|
+
},
|
|
2778
|
+
[respondToToolInput, runTurn, state.pendingInputs]
|
|
2779
|
+
);
|
|
1499
2780
|
useEffect(() => {
|
|
1500
2781
|
const conversation = loadPersistedAgentConversation(
|
|
1501
2782
|
resolvedStorageKeyPrefix,
|
|
@@ -1529,6 +2810,8 @@ ${outgoing}` : outgoing;
|
|
|
1529
2810
|
state,
|
|
1530
2811
|
reset,
|
|
1531
2812
|
retry,
|
|
2813
|
+
respondToInput,
|
|
2814
|
+
respondToToolInput,
|
|
1532
2815
|
submit,
|
|
1533
2816
|
rememberBooking,
|
|
1534
2817
|
forgetBooking,
|
|
@@ -1567,6 +2850,33 @@ function normalizeAgentPlacement(placement) {
|
|
|
1567
2850
|
};
|
|
1568
2851
|
}
|
|
1569
2852
|
|
|
2853
|
+
// src/react/panel-controller.ts
|
|
2854
|
+
var controllers = /* @__PURE__ */ new Map();
|
|
2855
|
+
function registerAgentPanelController(customerId, controller) {
|
|
2856
|
+
controllers.set(customerId, controller);
|
|
2857
|
+
}
|
|
2858
|
+
function unregisterAgentPanelController(customerId) {
|
|
2859
|
+
controllers.delete(customerId);
|
|
2860
|
+
}
|
|
2861
|
+
function openAgentPanel(customerId) {
|
|
2862
|
+
controllers.get(customerId)?.open();
|
|
2863
|
+
}
|
|
2864
|
+
function closeAgentPanel(customerId) {
|
|
2865
|
+
controllers.get(customerId)?.close();
|
|
2866
|
+
}
|
|
2867
|
+
function resetAgentPanel(customerId) {
|
|
2868
|
+
controllers.get(customerId)?.reset();
|
|
2869
|
+
}
|
|
2870
|
+
function submitAgentPanel(customerId, message, options) {
|
|
2871
|
+
const controller = controllers.get(customerId);
|
|
2872
|
+
if (!controller) {
|
|
2873
|
+
return Promise.reject(
|
|
2874
|
+
new Error(`Agent panel "${customerId}" is not mounted.`)
|
|
2875
|
+
);
|
|
2876
|
+
}
|
|
2877
|
+
return controller.submit(message, options);
|
|
2878
|
+
}
|
|
2879
|
+
|
|
1570
2880
|
// src/react/types/conversation.ts
|
|
1571
2881
|
var defaultAgentRailTheme = {
|
|
1572
2882
|
railMaxWidth: "450px",
|
|
@@ -1579,8 +2889,8 @@ var defaultAgentRailTheme = {
|
|
|
1579
2889
|
textMuted: "#5a6378",
|
|
1580
2890
|
textSubtle: "#8a94a8",
|
|
1581
2891
|
border: "rgb(42 51 70 / 0.1)",
|
|
1582
|
-
visitorBubble: "#
|
|
1583
|
-
visitorText: "#
|
|
2892
|
+
visitorBubble: "#f3edff",
|
|
2893
|
+
visitorText: "#171b2a",
|
|
1584
2894
|
success: "#18794e",
|
|
1585
2895
|
danger: "#c94b63",
|
|
1586
2896
|
fontBody: '"Mulish", "Avenir Next", "Segoe UI", sans-serif',
|
|
@@ -1597,13 +2907,14 @@ var defaultDarkAgentRailTheme = {
|
|
|
1597
2907
|
textMuted: "#b6bfce",
|
|
1598
2908
|
textSubtle: "#919cad",
|
|
1599
2909
|
border: "rgb(226 232 240 / 0.16)",
|
|
1600
|
-
visitorBubble: "#
|
|
2910
|
+
visitorBubble: "#2b2140",
|
|
2911
|
+
visitorText: "#f5f7fb",
|
|
1601
2912
|
success: "#55cf91",
|
|
1602
2913
|
danger: "#ff8da1"
|
|
1603
2914
|
};
|
|
1604
2915
|
|
|
1605
2916
|
// src/react/components/AgentRail/AgentRail.tsx
|
|
1606
|
-
import { useEffect as
|
|
2917
|
+
import { useEffect as useEffect3, useRef as useRef3, useState as useState7 } from "react";
|
|
1607
2918
|
|
|
1608
2919
|
// src/react/hooks/useAgentColorScheme.ts
|
|
1609
2920
|
import { useSyncExternalStore } from "react";
|
|
@@ -1637,11 +2948,21 @@ function resolveAgentColorScheme(colorScheme = "auto", prefersDarkMode) {
|
|
|
1637
2948
|
|
|
1638
2949
|
// src/react/components/AgentActivityBubble/AgentActivityBubble.tsx
|
|
1639
2950
|
import { useState as useState2 } from "react";
|
|
1640
|
-
import {
|
|
2951
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2952
|
+
function joinLabels(labels) {
|
|
2953
|
+
if (labels.length <= 1) return labels[0] ?? "";
|
|
2954
|
+
if (labels.length === 2) return `${labels[0]} and ${labels[1]}`;
|
|
2955
|
+
return `${labels.slice(0, -1).join(", ")} and ${labels.at(-1) ?? ""}`;
|
|
2956
|
+
}
|
|
1641
2957
|
function workSummary(steps, failed, brandLabel) {
|
|
2958
|
+
const activeSpecialists = steps.filter(
|
|
2959
|
+
(step) => step.kind === "specialist" && step.state === "active"
|
|
2960
|
+
);
|
|
2961
|
+
if (activeSpecialists.length > 0)
|
|
2962
|
+
return `Working with ${joinLabels(
|
|
2963
|
+
activeSpecialists.map((step) => step.label)
|
|
2964
|
+
)}`;
|
|
1642
2965
|
const active = [...steps].reverse().find((step) => step.state === "active");
|
|
1643
|
-
if (active?.kind === "specialist")
|
|
1644
|
-
return `${active.label} is reviewing your question`;
|
|
1645
2966
|
if (active?.kind === "search") return "Searching this site";
|
|
1646
2967
|
if (active)
|
|
1647
2968
|
return brandLabel ? `${brandLabel} is choosing the best way to help` : "Choosing the best way to help";
|
|
@@ -1659,7 +2980,7 @@ function workSummary(steps, failed, brandLabel) {
|
|
|
1659
2980
|
if (specialists.length === 1)
|
|
1660
2981
|
return `Brought in ${specialists[0]?.label}`;
|
|
1661
2982
|
if (searched) return "Searched this site";
|
|
1662
|
-
return "Answer ready";
|
|
2983
|
+
return brandLabel ? `Answered with ${brandLabel}` : "Answer ready";
|
|
1663
2984
|
}
|
|
1664
2985
|
function stepLabel(step, brandLabel) {
|
|
1665
2986
|
return step.kind === "planning" ? brandLabel : step.label;
|
|
@@ -1677,36 +2998,11 @@ function stepDetail(step, steps) {
|
|
|
1677
2998
|
return "Searched this site";
|
|
1678
2999
|
return step.detail;
|
|
1679
3000
|
}
|
|
1680
|
-
function SearchIcon() {
|
|
1681
|
-
return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: [
|
|
1682
|
-
/* @__PURE__ */ jsx("circle", { cx: "7", cy: "7", r: "3.75", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
1683
|
-
/* @__PURE__ */ jsx(
|
|
1684
|
-
"path",
|
|
1685
|
-
{
|
|
1686
|
-
d: "m10 10 3 3",
|
|
1687
|
-
stroke: "currentColor",
|
|
1688
|
-
strokeWidth: "1.4",
|
|
1689
|
-
strokeLinecap: "round"
|
|
1690
|
-
}
|
|
1691
|
-
)
|
|
1692
|
-
] });
|
|
1693
|
-
}
|
|
1694
|
-
function PlanningIcon() {
|
|
1695
|
-
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
|
|
1696
|
-
"path",
|
|
1697
|
-
{
|
|
1698
|
-
d: "M4 4.5h8M4 8h5.5M4 11.5h7",
|
|
1699
|
-
stroke: "currentColor",
|
|
1700
|
-
strokeWidth: "1.4",
|
|
1701
|
-
strokeLinecap: "round"
|
|
1702
|
-
}
|
|
1703
|
-
) });
|
|
1704
|
-
}
|
|
1705
3001
|
function AgentActivityBubble({
|
|
1706
3002
|
brandLabel = "",
|
|
1707
|
-
brandLogoUrl,
|
|
1708
3003
|
failed = false,
|
|
1709
|
-
steps
|
|
3004
|
+
steps,
|
|
3005
|
+
onRetryStep
|
|
1710
3006
|
}) {
|
|
1711
3007
|
const active = steps.some((step) => step.state === "active");
|
|
1712
3008
|
const receiptId = steps.map((step) => step.id).join(":");
|
|
@@ -1714,6 +3010,10 @@ function AgentActivityBubble({
|
|
|
1714
3010
|
null
|
|
1715
3011
|
);
|
|
1716
3012
|
const detailsOpen = active || expandedReceiptId === receiptId;
|
|
3013
|
+
const delegationCount = steps.filter(
|
|
3014
|
+
(step) => step.kind === "specialist"
|
|
3015
|
+
).length;
|
|
3016
|
+
const delegated = delegationCount > 0 && steps.some((step) => step.kind === "planning");
|
|
1717
3017
|
const visibleSteps = steps.filter(
|
|
1718
3018
|
(step) => step.kind !== "planning" || Boolean(brandLabel)
|
|
1719
3019
|
);
|
|
@@ -1746,10 +3046,11 @@ function AgentActivityBubble({
|
|
|
1746
3046
|
),
|
|
1747
3047
|
detailsOpen ? /* @__PURE__ */ jsx("ol", { className: "agent-activity-bubble__steps", children: visibleSteps.map((step) => {
|
|
1748
3048
|
const detail = stepDetail(step, steps);
|
|
3049
|
+
const child = delegated && step.kind === "specialist";
|
|
1749
3050
|
return /* @__PURE__ */ jsxs(
|
|
1750
3051
|
"li",
|
|
1751
3052
|
{
|
|
1752
|
-
className: "agent-activity-bubble__step"
|
|
3053
|
+
className: `agent-activity-bubble__step${child ? " agent-activity-bubble__step--child" : ""}`,
|
|
1753
3054
|
"data-kind": step.kind,
|
|
1754
3055
|
"data-state": step.state,
|
|
1755
3056
|
children: [
|
|
@@ -1757,25 +3058,27 @@ function AgentActivityBubble({
|
|
|
1757
3058
|
"span",
|
|
1758
3059
|
{
|
|
1759
3060
|
className: "agent-activity-bubble__step-icon",
|
|
1760
|
-
"aria-hidden": "true"
|
|
1761
|
-
children: step.kind === "planning" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1762
|
-
/* @__PURE__ */ jsx(PlanningIcon, {}),
|
|
1763
|
-
brandLogoUrl ? /* @__PURE__ */ jsx(
|
|
1764
|
-
"img",
|
|
1765
|
-
{
|
|
1766
|
-
src: brandLogoUrl,
|
|
1767
|
-
alt: "",
|
|
1768
|
-
onError: (event) => {
|
|
1769
|
-
event.currentTarget.hidden = true;
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
) : null
|
|
1773
|
-
] }) : step.kind === "search" ? /* @__PURE__ */ jsx(SearchIcon, {}) : step.label.slice(0, 1).toUpperCase()
|
|
3061
|
+
"aria-hidden": "true"
|
|
1774
3062
|
}
|
|
1775
3063
|
),
|
|
1776
3064
|
/* @__PURE__ */ jsxs("span", { className: "agent-activity-bubble__step-copy", children: [
|
|
1777
3065
|
/* @__PURE__ */ jsx("span", { className: "agent-activity-bubble__step-heading", children: /* @__PURE__ */ jsx("strong", { children: stepLabel(step, brandLabel) }) }),
|
|
1778
|
-
detail ? /* @__PURE__ */ jsx("span", { className: "agent-activity-bubble__step-detail", children: detail }) : null
|
|
3066
|
+
detail ? /* @__PURE__ */ jsx("span", { className: "agent-activity-bubble__step-detail", children: detail }) : null,
|
|
3067
|
+
delegated && step.kind === "planning" ? /* @__PURE__ */ jsxs("span", { className: "agent-activity-bubble__delegation", children: [
|
|
3068
|
+
"Delegated ",
|
|
3069
|
+
delegationCount,
|
|
3070
|
+
" ",
|
|
3071
|
+
delegationCount === 1 ? "task" : "tasks"
|
|
3072
|
+
] }) : null,
|
|
3073
|
+
step.state === "error" && onRetryStep ? /* @__PURE__ */ jsx(
|
|
3074
|
+
"button",
|
|
3075
|
+
{
|
|
3076
|
+
type: "button",
|
|
3077
|
+
className: "agent-activity-bubble__step-retry",
|
|
3078
|
+
onClick: () => onRetryStep(step),
|
|
3079
|
+
children: "Retry"
|
|
3080
|
+
}
|
|
3081
|
+
) : null
|
|
1779
3082
|
] })
|
|
1780
3083
|
]
|
|
1781
3084
|
},
|
|
@@ -1787,62 +3090,198 @@ function AgentActivityBubble({
|
|
|
1787
3090
|
}
|
|
1788
3091
|
|
|
1789
3092
|
// src/react/components/Composer/Composer.tsx
|
|
1790
|
-
import {
|
|
3093
|
+
import {
|
|
3094
|
+
useEffect as useEffect2,
|
|
3095
|
+
useId,
|
|
3096
|
+
useRef as useRef2,
|
|
3097
|
+
useState as useState3
|
|
3098
|
+
} from "react";
|
|
1791
3099
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1792
3100
|
function SendIcon() {
|
|
1793
|
-
return /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(
|
|
3101
|
+
return /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(
|
|
3102
|
+
"path",
|
|
3103
|
+
{
|
|
3104
|
+
d: "M8 12V4M8 4l-3 3M8 4l3 3",
|
|
3105
|
+
stroke: "currentColor",
|
|
3106
|
+
strokeWidth: "1.5",
|
|
3107
|
+
strokeLinecap: "round",
|
|
3108
|
+
strokeLinejoin: "round"
|
|
3109
|
+
}
|
|
3110
|
+
) });
|
|
3111
|
+
}
|
|
3112
|
+
function emptyValues(form) {
|
|
3113
|
+
const values = {};
|
|
3114
|
+
for (const field of form?.fields ?? []) values[field.id] = "";
|
|
3115
|
+
return values;
|
|
1794
3116
|
}
|
|
1795
3117
|
function Composer({
|
|
1796
3118
|
disabled = false,
|
|
1797
3119
|
placeholder = "Ask anything\u2026",
|
|
1798
3120
|
variant = "default",
|
|
3121
|
+
form = null,
|
|
1799
3122
|
onSubmit
|
|
1800
3123
|
}) {
|
|
1801
3124
|
const [value, setValue] = useState3("");
|
|
3125
|
+
const [values, setValues] = useState3(
|
|
3126
|
+
() => emptyValues(form)
|
|
3127
|
+
);
|
|
3128
|
+
const [blurred, setBlurred] = useState3({});
|
|
1802
3129
|
const inputRef = useRef2(null);
|
|
1803
|
-
|
|
3130
|
+
const firstFieldRef = useRef2(null);
|
|
3131
|
+
const formId = useId();
|
|
3132
|
+
const activeForm = form;
|
|
3133
|
+
const canSendForm = activeForm ? isComposerFormComplete(activeForm, values) : Boolean(value.trim());
|
|
3134
|
+
useEffect2(() => {
|
|
3135
|
+
setValues(emptyValues(form));
|
|
3136
|
+
setBlurred({});
|
|
3137
|
+
}, [form?.id]);
|
|
3138
|
+
useEffect2(() => {
|
|
3139
|
+
if (activeForm) firstFieldRef.current?.focus();
|
|
3140
|
+
}, [activeForm?.id]);
|
|
3141
|
+
function submitChat() {
|
|
1804
3142
|
const trimmed = value.trim();
|
|
1805
3143
|
if (!trimmed || disabled) return;
|
|
1806
3144
|
onSubmit?.(trimmed);
|
|
1807
3145
|
setValue("");
|
|
1808
3146
|
inputRef.current?.focus();
|
|
1809
3147
|
}
|
|
3148
|
+
function submitForm() {
|
|
3149
|
+
if (!activeForm || disabled || !canSendForm) return;
|
|
3150
|
+
onSubmit?.(formatComposerFormMessage(activeForm, values));
|
|
3151
|
+
setValues(emptyValues(activeForm));
|
|
3152
|
+
setBlurred({});
|
|
3153
|
+
}
|
|
1810
3154
|
function handleSubmit(event) {
|
|
1811
3155
|
event.preventDefault();
|
|
1812
|
-
|
|
3156
|
+
if (activeForm) submitForm();
|
|
3157
|
+
else submitChat();
|
|
1813
3158
|
}
|
|
1814
|
-
function
|
|
3159
|
+
function handleChatKeyDown(event) {
|
|
1815
3160
|
if (event.key === "Enter" && !event.shiftKey) {
|
|
1816
3161
|
event.preventDefault();
|
|
1817
|
-
|
|
3162
|
+
submitChat();
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
function handleFormKeyDown(event) {
|
|
3166
|
+
const target = event.target;
|
|
3167
|
+
const isTextarea = Boolean(target && "tagName" in target && target.tagName === "TEXTAREA");
|
|
3168
|
+
if (event.key === "Enter" && !event.shiftKey && !isTextarea) {
|
|
3169
|
+
event.preventDefault();
|
|
3170
|
+
submitForm();
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
return /* @__PURE__ */ jsx2(
|
|
3174
|
+
"form",
|
|
3175
|
+
{
|
|
3176
|
+
className: [
|
|
3177
|
+
"composer",
|
|
3178
|
+
variant === "dock" ? "composer--dock" : "",
|
|
3179
|
+
activeForm ? "composer--form" : ""
|
|
3180
|
+
].filter(Boolean).join(" "),
|
|
3181
|
+
onSubmit: handleSubmit,
|
|
3182
|
+
children: activeForm ? /* @__PURE__ */ jsxs2("div", { className: "composer__sheet", role: "group", "aria-label": "Required details", children: [
|
|
3183
|
+
activeForm.fields.map((field, index) => {
|
|
3184
|
+
const fieldId = `${formId}-${field.id}`;
|
|
3185
|
+
const invalid = Boolean(blurred[field.id]) && !isValidComposerFieldValue(field, values[field.id] ?? "");
|
|
3186
|
+
const controlProps = {
|
|
3187
|
+
id: fieldId,
|
|
3188
|
+
name: field.id,
|
|
3189
|
+
disabled,
|
|
3190
|
+
required: field.required,
|
|
3191
|
+
autoComplete: field.autocomplete,
|
|
3192
|
+
placeholder: field.placeholder,
|
|
3193
|
+
spellCheck: false,
|
|
3194
|
+
value: values[field.id] ?? "",
|
|
3195
|
+
"aria-invalid": invalid || void 0,
|
|
3196
|
+
"aria-describedby": invalid ? `${fieldId}-error` : void 0,
|
|
3197
|
+
onBlur: () => setBlurred((current) => ({ ...current, [field.id]: true })),
|
|
3198
|
+
onChange: (event) => {
|
|
3199
|
+
const next = readComposerControlValue(event);
|
|
3200
|
+
setValues((current) => ({
|
|
3201
|
+
...current,
|
|
3202
|
+
[field.id]: next
|
|
3203
|
+
}));
|
|
3204
|
+
},
|
|
3205
|
+
onKeyDown: handleFormKeyDown
|
|
3206
|
+
};
|
|
3207
|
+
return /* @__PURE__ */ jsxs2(
|
|
3208
|
+
"div",
|
|
3209
|
+
{
|
|
3210
|
+
className: [
|
|
3211
|
+
"composer__row",
|
|
3212
|
+
field.kind === "textarea" ? "composer__row--grow" : ""
|
|
3213
|
+
].filter(Boolean).join(" "),
|
|
3214
|
+
children: [
|
|
3215
|
+
/* @__PURE__ */ jsxs2("label", { className: "composer__label", htmlFor: fieldId, children: [
|
|
3216
|
+
/* @__PURE__ */ jsx2("span", { className: "composer__sr-only", children: field.label }),
|
|
3217
|
+
field.kind === "textarea" ? /* @__PURE__ */ jsx2(
|
|
3218
|
+
"textarea",
|
|
3219
|
+
{
|
|
3220
|
+
...controlProps,
|
|
3221
|
+
ref: index === 0 ? (node) => {
|
|
3222
|
+
firstFieldRef.current = node;
|
|
3223
|
+
} : void 0,
|
|
3224
|
+
className: "composer__control composer__control--area",
|
|
3225
|
+
rows: 3
|
|
3226
|
+
}
|
|
3227
|
+
) : /* @__PURE__ */ jsx2(
|
|
3228
|
+
"input",
|
|
3229
|
+
{
|
|
3230
|
+
...controlProps,
|
|
3231
|
+
ref: index === 0 ? (node) => {
|
|
3232
|
+
firstFieldRef.current = node;
|
|
3233
|
+
} : void 0,
|
|
3234
|
+
className: "composer__control",
|
|
3235
|
+
type: field.kind,
|
|
3236
|
+
inputMode: field.kind === "tel" ? "tel" : void 0
|
|
3237
|
+
}
|
|
3238
|
+
)
|
|
3239
|
+
] }),
|
|
3240
|
+
invalid ? /* @__PURE__ */ jsx2("p", { className: "composer__error", id: `${fieldId}-error`, children: field.kind === "email" ? "Enter a valid email to continue." : `Add your ${field.label.toLowerCase()} to continue.` }) : null
|
|
3241
|
+
]
|
|
3242
|
+
},
|
|
3243
|
+
field.id
|
|
3244
|
+
);
|
|
3245
|
+
}),
|
|
3246
|
+
/* @__PURE__ */ jsx2("div", { className: "composer__toolbar", children: /* @__PURE__ */ jsx2(
|
|
3247
|
+
"button",
|
|
3248
|
+
{
|
|
3249
|
+
type: "submit",
|
|
3250
|
+
className: "composer__send",
|
|
3251
|
+
disabled: disabled || !canSendForm,
|
|
3252
|
+
"aria-label": "Send details",
|
|
3253
|
+
children: /* @__PURE__ */ jsx2(SendIcon, {})
|
|
3254
|
+
}
|
|
3255
|
+
) })
|
|
3256
|
+
] }) : /* @__PURE__ */ jsxs2("div", { className: "composer__field", children: [
|
|
3257
|
+
/* @__PURE__ */ jsx2(
|
|
3258
|
+
"textarea",
|
|
3259
|
+
{
|
|
3260
|
+
ref: inputRef,
|
|
3261
|
+
className: "composer__input",
|
|
3262
|
+
rows: 1,
|
|
3263
|
+
value,
|
|
3264
|
+
placeholder,
|
|
3265
|
+
disabled,
|
|
3266
|
+
spellCheck: false,
|
|
3267
|
+
"aria-label": "Message",
|
|
3268
|
+
onChange: (event) => setValue(event.target.value),
|
|
3269
|
+
onKeyDown: handleChatKeyDown
|
|
3270
|
+
}
|
|
3271
|
+
),
|
|
3272
|
+
/* @__PURE__ */ jsx2(
|
|
3273
|
+
"button",
|
|
3274
|
+
{
|
|
3275
|
+
type: "submit",
|
|
3276
|
+
className: "composer__send",
|
|
3277
|
+
disabled: disabled || !value.trim(),
|
|
3278
|
+
"aria-label": "Send message",
|
|
3279
|
+
children: /* @__PURE__ */ jsx2(SendIcon, {})
|
|
3280
|
+
}
|
|
3281
|
+
)
|
|
3282
|
+
] })
|
|
1818
3283
|
}
|
|
1819
|
-
|
|
1820
|
-
return /* @__PURE__ */ jsx2("form", { className: `composer${variant === "dock" ? " composer--dock" : ""}`, onSubmit: handleSubmit, children: /* @__PURE__ */ jsxs2("div", { className: "composer__field", children: [
|
|
1821
|
-
/* @__PURE__ */ jsx2(
|
|
1822
|
-
"textarea",
|
|
1823
|
-
{
|
|
1824
|
-
ref: inputRef,
|
|
1825
|
-
className: "composer__input",
|
|
1826
|
-
rows: 1,
|
|
1827
|
-
value,
|
|
1828
|
-
placeholder,
|
|
1829
|
-
disabled,
|
|
1830
|
-
"aria-label": "Message",
|
|
1831
|
-
onChange: (event) => setValue(event.target.value),
|
|
1832
|
-
onKeyDown: handleKeyDown
|
|
1833
|
-
}
|
|
1834
|
-
),
|
|
1835
|
-
/* @__PURE__ */ jsx2(
|
|
1836
|
-
"button",
|
|
1837
|
-
{
|
|
1838
|
-
type: "submit",
|
|
1839
|
-
className: "composer__send",
|
|
1840
|
-
disabled: disabled || !value.trim(),
|
|
1841
|
-
"aria-label": "Send message",
|
|
1842
|
-
children: /* @__PURE__ */ jsx2(SendIcon, {})
|
|
1843
|
-
}
|
|
1844
|
-
)
|
|
1845
|
-
] }) });
|
|
3284
|
+
);
|
|
1846
3285
|
}
|
|
1847
3286
|
|
|
1848
3287
|
// src/react/components/FollowUpChips/FollowUpChips.tsx
|
|
@@ -1888,11 +3327,17 @@ function FollowUpChips({
|
|
|
1888
3327
|
import { useState as useState5 } from "react";
|
|
1889
3328
|
|
|
1890
3329
|
// src/react/components/BookingCard/BookingCard.tsx
|
|
1891
|
-
import { useId, useMemo as useMemo2, useState as useState4 } from "react";
|
|
3330
|
+
import { useId as useId2, useMemo as useMemo2, useState as useState4 } from "react";
|
|
1892
3331
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
3332
|
+
var BOOKING_STEPS = [
|
|
3333
|
+
{ id: "date", label: "Date" },
|
|
3334
|
+
{ id: "time", label: "Time" },
|
|
3335
|
+
{ id: "details", label: "Details" }
|
|
3336
|
+
];
|
|
1893
3337
|
function monthFromKey(key) {
|
|
1894
3338
|
const [year, month] = key.split("-").map(Number);
|
|
1895
|
-
if (!year || !month)
|
|
3339
|
+
if (!year || !month)
|
|
3340
|
+
return { year: (/* @__PURE__ */ new Date()).getFullYear(), month: (/* @__PURE__ */ new Date()).getMonth() };
|
|
1896
3341
|
return { year, month: month - 1 };
|
|
1897
3342
|
}
|
|
1898
3343
|
function dateKeyFromParts(year, month, day) {
|
|
@@ -1917,7 +3362,7 @@ function BookingCard({
|
|
|
1917
3362
|
offer,
|
|
1918
3363
|
onBook
|
|
1919
3364
|
}) {
|
|
1920
|
-
const fieldId =
|
|
3365
|
+
const fieldId = useId2();
|
|
1921
3366
|
const defaultType = offer.eventTypes[0]?.uri ?? offer.slots[0]?.eventTypeUri ?? "";
|
|
1922
3367
|
const [step, setStep] = useState4("date");
|
|
1923
3368
|
const [eventTypeUri, setEventTypeUri] = useState4(defaultType);
|
|
@@ -1925,6 +3370,7 @@ function BookingCard({
|
|
|
1925
3370
|
const [startTime, setStartTime] = useState4("");
|
|
1926
3371
|
const [name, setName] = useState4("");
|
|
1927
3372
|
const [email, setEmail] = useState4("");
|
|
3373
|
+
const stepIndex = BOOKING_STEPS.findIndex((item) => item.id === step);
|
|
1928
3374
|
const slots = useMemo2(
|
|
1929
3375
|
() => bookingSlotsForEventType(offer.slots, eventTypeUri),
|
|
1930
3376
|
[eventTypeUri, offer.slots]
|
|
@@ -1945,14 +3391,18 @@ function BookingCard({
|
|
|
1945
3391
|
setSelectedDate("");
|
|
1946
3392
|
setStartTime("");
|
|
1947
3393
|
setVisibleMonth(
|
|
1948
|
-
firstAvailableBookingMonth(
|
|
3394
|
+
firstAvailableBookingMonth(
|
|
3395
|
+
bookingSlotsForEventType(offer.slots, nextType)
|
|
3396
|
+
)
|
|
1949
3397
|
);
|
|
1950
3398
|
}
|
|
1951
3399
|
const daySlots = useMemo2(
|
|
1952
3400
|
() => slots.filter((slot) => slotDateKey(slot.startTime) === selectedDate),
|
|
1953
3401
|
[selectedDate, slots]
|
|
1954
3402
|
);
|
|
1955
|
-
const selectedType = offer.eventTypes.find(
|
|
3403
|
+
const selectedType = offer.eventTypes.find(
|
|
3404
|
+
(item) => item.uri === eventTypeUri
|
|
3405
|
+
);
|
|
1956
3406
|
const selectedSample = availableByDate.get(selectedDate) ?? startTime;
|
|
1957
3407
|
const timeZone = formatSlotTimeZone(slots[0]?.startTime ?? selectedSample);
|
|
1958
3408
|
const weekdays = useMemo2(() => weekdayLabels(), []);
|
|
@@ -1998,24 +3448,47 @@ function BookingCard({
|
|
|
1998
3448
|
});
|
|
1999
3449
|
}
|
|
2000
3450
|
return /* @__PURE__ */ jsx4("section", { className: "booking-card", "aria-label": "Book a meeting", children: /* @__PURE__ */ jsxs4("form", { className: "booking-card__form", onSubmit: handleSubmit, children: [
|
|
3451
|
+
/* @__PURE__ */ jsx4("ol", { className: "booking-card__steps", "aria-label": "Booking steps", children: BOOKING_STEPS.map((item, index) => /* @__PURE__ */ jsxs4(
|
|
3452
|
+
"li",
|
|
3453
|
+
{
|
|
3454
|
+
className: [
|
|
3455
|
+
"booking-card__step-indicator",
|
|
3456
|
+
index === stepIndex ? "booking-card__step-indicator--active" : "",
|
|
3457
|
+
index < stepIndex ? "booking-card__step-indicator--complete" : ""
|
|
3458
|
+
].filter(Boolean).join(" "),
|
|
3459
|
+
"aria-current": index === stepIndex ? "step" : void 0,
|
|
3460
|
+
children: [
|
|
3461
|
+
/* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: index + 1 }),
|
|
3462
|
+
/* @__PURE__ */ jsx4("span", { children: item.label })
|
|
3463
|
+
]
|
|
3464
|
+
},
|
|
3465
|
+
item.id
|
|
3466
|
+
)) }),
|
|
2001
3467
|
step === "date" ? /* @__PURE__ */ jsxs4("div", { className: "booking-card__step", children: [
|
|
2002
3468
|
/* @__PURE__ */ jsx4("p", { className: "booking-card__title", children: selectedType?.name || "Pick a date" }),
|
|
2003
3469
|
timeZone ? /* @__PURE__ */ jsxs4("p", { className: "booking-card__tz", children: [
|
|
2004
3470
|
"Times in ",
|
|
2005
3471
|
timeZone
|
|
2006
3472
|
] }) : null,
|
|
2007
|
-
offer.eventTypes.length > 1 ? /* @__PURE__ */ jsxs4(
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
"
|
|
2011
|
-
{
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
3473
|
+
offer.eventTypes.length > 1 ? /* @__PURE__ */ jsxs4(
|
|
3474
|
+
"label",
|
|
3475
|
+
{
|
|
3476
|
+
className: "booking-card__field",
|
|
3477
|
+
htmlFor: `${fieldId}-type`,
|
|
3478
|
+
children: [
|
|
3479
|
+
/* @__PURE__ */ jsx4("span", { children: "Meeting" }),
|
|
3480
|
+
/* @__PURE__ */ jsx4(
|
|
3481
|
+
"select",
|
|
3482
|
+
{
|
|
3483
|
+
id: `${fieldId}-type`,
|
|
3484
|
+
value: eventTypeUri,
|
|
3485
|
+
onChange: (event) => selectEventType(event.target.value),
|
|
3486
|
+
children: offer.eventTypes.map((item) => /* @__PURE__ */ jsx4("option", { value: item.uri, children: item.name }, item.uri))
|
|
3487
|
+
}
|
|
3488
|
+
)
|
|
3489
|
+
]
|
|
3490
|
+
}
|
|
3491
|
+
) : null,
|
|
2019
3492
|
/* @__PURE__ */ jsxs4("div", { className: "booking-card__month", children: [
|
|
2020
3493
|
/* @__PURE__ */ jsx4(
|
|
2021
3494
|
"button",
|
|
@@ -2042,29 +3515,44 @@ function BookingCard({
|
|
|
2042
3515
|
)
|
|
2043
3516
|
] }),
|
|
2044
3517
|
/* @__PURE__ */ jsx4("div", { className: "booking-card__weekdays", children: weekdays.map((label) => /* @__PURE__ */ jsx4("span", { children: label }, label)) }),
|
|
2045
|
-
/* @__PURE__ */ jsx4("
|
|
2046
|
-
|
|
2047
|
-
|
|
3518
|
+
offer.slots.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "booking-card__loading", role: "status", children: "Finding available times\u2026" }) : null,
|
|
3519
|
+
/* @__PURE__ */ jsx4(
|
|
3520
|
+
"div",
|
|
3521
|
+
{
|
|
3522
|
+
className: "booking-card__calendar",
|
|
3523
|
+
role: "grid",
|
|
3524
|
+
"aria-label": "Available dates",
|
|
3525
|
+
children: cells.map((cell, index) => {
|
|
3526
|
+
if (!cell) {
|
|
3527
|
+
return /* @__PURE__ */ jsx4(
|
|
3528
|
+
"span",
|
|
3529
|
+
{
|
|
3530
|
+
className: "booking-card__day"
|
|
3531
|
+
},
|
|
3532
|
+
`empty-${index}`
|
|
3533
|
+
);
|
|
3534
|
+
}
|
|
3535
|
+
const available = availableByDate.has(cell.key);
|
|
3536
|
+
const selected = cell.key === selectedDate;
|
|
3537
|
+
return /* @__PURE__ */ jsx4(
|
|
3538
|
+
"button",
|
|
3539
|
+
{
|
|
3540
|
+
type: "button",
|
|
3541
|
+
className: [
|
|
3542
|
+
"booking-card__day",
|
|
3543
|
+
available ? "booking-card__day--available" : "",
|
|
3544
|
+
selected ? "booking-card__day--selected" : ""
|
|
3545
|
+
].filter(Boolean).join(" "),
|
|
3546
|
+
disabled: !available,
|
|
3547
|
+
"aria-pressed": selected,
|
|
3548
|
+
onClick: () => selectDate(cell.key),
|
|
3549
|
+
children: cell.day
|
|
3550
|
+
},
|
|
3551
|
+
cell.key
|
|
3552
|
+
);
|
|
3553
|
+
})
|
|
2048
3554
|
}
|
|
2049
|
-
|
|
2050
|
-
const selected = cell.key === selectedDate;
|
|
2051
|
-
return /* @__PURE__ */ jsx4(
|
|
2052
|
-
"button",
|
|
2053
|
-
{
|
|
2054
|
-
type: "button",
|
|
2055
|
-
className: [
|
|
2056
|
-
"booking-card__day",
|
|
2057
|
-
available ? "booking-card__day--available" : "",
|
|
2058
|
-
selected ? "booking-card__day--selected" : ""
|
|
2059
|
-
].filter(Boolean).join(" "),
|
|
2060
|
-
disabled: !available,
|
|
2061
|
-
"aria-pressed": selected,
|
|
2062
|
-
onClick: () => selectDate(cell.key),
|
|
2063
|
-
children: cell.day
|
|
2064
|
-
},
|
|
2065
|
-
cell.key
|
|
2066
|
-
);
|
|
2067
|
-
}) })
|
|
3555
|
+
)
|
|
2068
3556
|
] }, "date") : null,
|
|
2069
3557
|
step === "time" ? /* @__PURE__ */ jsxs4("div", { className: "booking-card__step", children: [
|
|
2070
3558
|
/* @__PURE__ */ jsxs4("div", { className: "booking-card__step-bar", children: [
|
|
@@ -2116,35 +3604,57 @@ function BookingCard({
|
|
|
2116
3604
|
] })
|
|
2117
3605
|
] }),
|
|
2118
3606
|
/* @__PURE__ */ jsxs4("div", { className: "booking-card__identity", children: [
|
|
2119
|
-
/* @__PURE__ */ jsxs4(
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
"
|
|
2123
|
-
{
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
}
|
|
2144
|
-
|
|
2145
|
-
|
|
3607
|
+
/* @__PURE__ */ jsxs4(
|
|
3608
|
+
"label",
|
|
3609
|
+
{
|
|
3610
|
+
className: "booking-card__field",
|
|
3611
|
+
htmlFor: `${fieldId}-name`,
|
|
3612
|
+
children: [
|
|
3613
|
+
/* @__PURE__ */ jsx4("span", { children: "Name" }),
|
|
3614
|
+
/* @__PURE__ */ jsx4(
|
|
3615
|
+
"input",
|
|
3616
|
+
{
|
|
3617
|
+
id: `${fieldId}-name`,
|
|
3618
|
+
autoComplete: "name",
|
|
3619
|
+
value: name,
|
|
3620
|
+
onChange: (event) => setName(event.target.value),
|
|
3621
|
+
required: true
|
|
3622
|
+
}
|
|
3623
|
+
)
|
|
3624
|
+
]
|
|
3625
|
+
}
|
|
3626
|
+
),
|
|
3627
|
+
/* @__PURE__ */ jsxs4(
|
|
3628
|
+
"label",
|
|
3629
|
+
{
|
|
3630
|
+
className: "booking-card__field",
|
|
3631
|
+
htmlFor: `${fieldId}-email`,
|
|
3632
|
+
children: [
|
|
3633
|
+
/* @__PURE__ */ jsx4("span", { children: "Email" }),
|
|
3634
|
+
/* @__PURE__ */ jsx4(
|
|
3635
|
+
"input",
|
|
3636
|
+
{
|
|
3637
|
+
id: `${fieldId}-email`,
|
|
3638
|
+
type: "email",
|
|
3639
|
+
autoComplete: "email",
|
|
3640
|
+
value: email,
|
|
3641
|
+
onChange: (event) => setEmail(event.target.value),
|
|
3642
|
+
required: true
|
|
3643
|
+
}
|
|
3644
|
+
)
|
|
3645
|
+
]
|
|
3646
|
+
}
|
|
3647
|
+
)
|
|
2146
3648
|
] }),
|
|
2147
|
-
/* @__PURE__ */ jsx4(
|
|
3649
|
+
/* @__PURE__ */ jsx4(
|
|
3650
|
+
"button",
|
|
3651
|
+
{
|
|
3652
|
+
type: "submit",
|
|
3653
|
+
className: "booking-card__submit",
|
|
3654
|
+
disabled: !name.trim() || !email.trim(),
|
|
3655
|
+
children: "Book this time"
|
|
3656
|
+
}
|
|
3657
|
+
)
|
|
2148
3658
|
] }, "details") : null
|
|
2149
3659
|
] }) });
|
|
2150
3660
|
}
|
|
@@ -2153,6 +3663,43 @@ function BookingCard({
|
|
|
2153
3663
|
import { Streamdown } from "streamdown";
|
|
2154
3664
|
import "streamdown/styles.css";
|
|
2155
3665
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
3666
|
+
function normalizeDedupeText(text) {
|
|
3667
|
+
return text.trim().replace(/\s+/g, " ").toLowerCase();
|
|
3668
|
+
}
|
|
3669
|
+
function paragraphsAreNearDuplicates(first, second) {
|
|
3670
|
+
const left = normalizeDedupeText(first);
|
|
3671
|
+
const right = normalizeDedupeText(second);
|
|
3672
|
+
if (left.length < 40 || right.length < 40) return false;
|
|
3673
|
+
if (left === right) return true;
|
|
3674
|
+
const shorter = left.length <= right.length ? left : right;
|
|
3675
|
+
const longer = left.length <= right.length ? right : left;
|
|
3676
|
+
return longer.startsWith(
|
|
3677
|
+
shorter.slice(0, Math.floor(shorter.length * 0.85))
|
|
3678
|
+
);
|
|
3679
|
+
}
|
|
3680
|
+
function paragraphsShareOpening(first, second) {
|
|
3681
|
+
const opening = first.split("\n")[0]?.trim();
|
|
3682
|
+
if (!opening || opening.length < 20) return false;
|
|
3683
|
+
return second.trim().startsWith(opening);
|
|
3684
|
+
}
|
|
3685
|
+
function collapseRepeatedText(text) {
|
|
3686
|
+
const trimmed = text.trim();
|
|
3687
|
+
if (trimmed.length < 40) return trimmed;
|
|
3688
|
+
const paragraphs = trimmed.split(/\n{2,}/u).map((part) => part.trim()).filter(Boolean);
|
|
3689
|
+
if (paragraphs.length === 2 && (paragraphsAreNearDuplicates(paragraphs[0], paragraphs[1]) || paragraphsShareOpening(paragraphs[0], paragraphs[1]))) {
|
|
3690
|
+
return paragraphs[0];
|
|
3691
|
+
}
|
|
3692
|
+
if (paragraphs.length >= 2 && paragraphs.length % 2 === 0) {
|
|
3693
|
+
const mid2 = paragraphs.length / 2;
|
|
3694
|
+
const first = paragraphs.slice(0, mid2).join("\n\n");
|
|
3695
|
+
const second = paragraphs.slice(mid2).join("\n\n");
|
|
3696
|
+
if (first === second) return first;
|
|
3697
|
+
}
|
|
3698
|
+
const mid = Math.floor(trimmed.length / 2);
|
|
3699
|
+
const left = trimmed.slice(0, mid).trim();
|
|
3700
|
+
const right = trimmed.slice(mid).trim();
|
|
3701
|
+
return left.length >= 20 && left === right ? left : trimmed;
|
|
3702
|
+
}
|
|
2156
3703
|
function MessageBubble({
|
|
2157
3704
|
message,
|
|
2158
3705
|
brandLogoUrl,
|
|
@@ -2169,24 +3716,41 @@ function MessageBubble({
|
|
|
2169
3716
|
const offers = offer ? [offer] : extractedOffers;
|
|
2170
3717
|
const visibleText = hideToolCardFences(message.text);
|
|
2171
3718
|
const isStreaming = message.role === "agent" && Boolean(message.streaming);
|
|
2172
|
-
const displayText =
|
|
3719
|
+
const displayText = collapseRepeatedText(
|
|
3720
|
+
offers.length > 0 ? sanitizeBookingOfferCopy(visibleText) : looksLikeBookingAvailabilityDump(visibleText) ? sanitizeBookingOfferCopy(visibleText) : visibleText || (isStreaming ? "" : message.text)
|
|
3721
|
+
);
|
|
2173
3722
|
if (message.role === "visitor") {
|
|
2174
3723
|
return /* @__PURE__ */ jsx5("article", { className: "message-bubble message-bubble--visitor", children: /* @__PURE__ */ jsx5("p", { className: "message-bubble__text", children: message.text }) });
|
|
2175
3724
|
}
|
|
2176
|
-
const
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
3725
|
+
const citations = message.citations ?? [];
|
|
3726
|
+
const agentText = /* @__PURE__ */ jsxs5("div", { className: "message-bubble__text", children: [
|
|
3727
|
+
/* @__PURE__ */ jsx5(
|
|
3728
|
+
Streamdown,
|
|
3729
|
+
{
|
|
3730
|
+
animated: isStreaming,
|
|
3731
|
+
caret: "circle",
|
|
3732
|
+
className: "message-bubble__markdown",
|
|
3733
|
+
controls: false,
|
|
3734
|
+
isAnimating: isStreaming,
|
|
3735
|
+
linkSafety: { enabled: false },
|
|
3736
|
+
mode: isStreaming ? "streaming" : "static",
|
|
3737
|
+
skipHtml: true,
|
|
3738
|
+
children: displayText
|
|
3739
|
+
}
|
|
3740
|
+
),
|
|
3741
|
+
citations.length > 0 ? /* @__PURE__ */ jsx5("ul", { className: "message-bubble__sources", "aria-label": "Sources", children: citations.map((citation) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsxs5(
|
|
3742
|
+
"a",
|
|
3743
|
+
{
|
|
3744
|
+
href: citation.url,
|
|
3745
|
+
target: "_blank",
|
|
3746
|
+
rel: "noreferrer",
|
|
3747
|
+
children: [
|
|
3748
|
+
/* @__PURE__ */ jsx5("span", { className: "message-bubble__source-icon", "aria-hidden": "true", children: "\u25A6" }),
|
|
3749
|
+
/* @__PURE__ */ jsx5("span", { children: citation.label })
|
|
3750
|
+
]
|
|
3751
|
+
}
|
|
3752
|
+
) }, citation.id)) }) : null
|
|
3753
|
+
] });
|
|
2190
3754
|
return /* @__PURE__ */ jsxs5("article", { className: "message-bubble message-bubble--agent", children: [
|
|
2191
3755
|
displayText ? showBrandLogo ? /* @__PURE__ */ jsxs5("div", { className: "message-bubble__agent-row", children: [
|
|
2192
3756
|
/* @__PURE__ */ jsx5("span", { className: "message-bubble__agent-avatar", "aria-hidden": "true", children: /* @__PURE__ */ jsx5(
|
|
@@ -2212,10 +3776,262 @@ function MessageBubble({
|
|
|
2212
3776
|
] });
|
|
2213
3777
|
}
|
|
2214
3778
|
|
|
2215
|
-
// src/react/components/
|
|
3779
|
+
// src/react/components/HumanInputCard/HumanInputCard.tsx
|
|
3780
|
+
import { useState as useState6 } from "react";
|
|
3781
|
+
|
|
3782
|
+
// src/react/components/ConfirmationCard/ConfirmationCard.tsx
|
|
2216
3783
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
3784
|
+
function ConfirmationCard({
|
|
3785
|
+
disabled = false,
|
|
3786
|
+
request,
|
|
3787
|
+
onRespond
|
|
3788
|
+
}) {
|
|
3789
|
+
const options = request.options ?? [];
|
|
3790
|
+
const heading = request.kind === "tool-approval" ? "Confirm this action" : request.prompt;
|
|
3791
|
+
const prompt = request.kind === "tool-approval" ? request.prompt : void 0;
|
|
3792
|
+
return /* @__PURE__ */ jsxs6(
|
|
3793
|
+
"section",
|
|
3794
|
+
{
|
|
3795
|
+
className: "confirmation-card",
|
|
3796
|
+
"aria-labelledby": `confirmation-${request.requestId}`,
|
|
3797
|
+
children: [
|
|
3798
|
+
/* @__PURE__ */ jsxs6("div", { className: "confirmation-card__heading", children: [
|
|
3799
|
+
/* @__PURE__ */ jsx6("strong", { id: `confirmation-${request.requestId}`, children: heading }),
|
|
3800
|
+
prompt ? /* @__PURE__ */ jsx6("p", { children: prompt }) : null
|
|
3801
|
+
] }),
|
|
3802
|
+
/* @__PURE__ */ jsx6("div", { className: "confirmation-card__actions", children: options.map((option) => /* @__PURE__ */ jsx6(
|
|
3803
|
+
"button",
|
|
3804
|
+
{
|
|
3805
|
+
type: "button",
|
|
3806
|
+
className: `confirmation-card__action confirmation-card__action--${option.style ?? "default"}`,
|
|
3807
|
+
disabled,
|
|
3808
|
+
onClick: () => onRespond?.({
|
|
3809
|
+
requestId: request.requestId,
|
|
3810
|
+
optionId: option.id
|
|
3811
|
+
}),
|
|
3812
|
+
children: option.label
|
|
3813
|
+
},
|
|
3814
|
+
option.id
|
|
3815
|
+
)) })
|
|
3816
|
+
]
|
|
3817
|
+
}
|
|
3818
|
+
);
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
// src/react/components/HumanInputCard/HumanInputCard.tsx
|
|
3822
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3823
|
+
function HumanInputCard({
|
|
3824
|
+
disabled = false,
|
|
3825
|
+
request,
|
|
3826
|
+
onRespond
|
|
3827
|
+
}) {
|
|
3828
|
+
const [text, setText] = useState6("");
|
|
3829
|
+
const options = request.options ?? [];
|
|
3830
|
+
const showText = request.display === "text" || request.allowFreeform && options.length === 0;
|
|
3831
|
+
if (options.length > 0) {
|
|
3832
|
+
return /* @__PURE__ */ jsx7(
|
|
3833
|
+
ConfirmationCard,
|
|
3834
|
+
{
|
|
3835
|
+
disabled,
|
|
3836
|
+
request,
|
|
3837
|
+
onRespond
|
|
3838
|
+
}
|
|
3839
|
+
);
|
|
3840
|
+
}
|
|
3841
|
+
function submitText(event) {
|
|
3842
|
+
event.preventDefault();
|
|
3843
|
+
const value = text.trim();
|
|
3844
|
+
if (!value || disabled) return;
|
|
3845
|
+
onRespond?.({ requestId: request.requestId, text: value });
|
|
3846
|
+
}
|
|
3847
|
+
return /* @__PURE__ */ jsxs7(
|
|
3848
|
+
"section",
|
|
3849
|
+
{
|
|
3850
|
+
className: "human-input-card",
|
|
3851
|
+
"aria-labelledby": `human-input-${request.requestId}`,
|
|
3852
|
+
children: [
|
|
3853
|
+
/* @__PURE__ */ jsx7("div", { className: "human-input-card__heading", children: /* @__PURE__ */ jsx7("strong", { id: `human-input-${request.requestId}`, children: request.prompt }) }),
|
|
3854
|
+
showText ? /* @__PURE__ */ jsxs7("form", { onSubmit: submitText, children: [
|
|
3855
|
+
/* @__PURE__ */ jsx7("label", { htmlFor: `human-input-text-${request.requestId}`, children: "Response" }),
|
|
3856
|
+
/* @__PURE__ */ jsxs7("div", { children: [
|
|
3857
|
+
/* @__PURE__ */ jsx7(
|
|
3858
|
+
"input",
|
|
3859
|
+
{
|
|
3860
|
+
id: `human-input-text-${request.requestId}`,
|
|
3861
|
+
value: text,
|
|
3862
|
+
disabled,
|
|
3863
|
+
onChange: (event) => setText(event.target.value)
|
|
3864
|
+
}
|
|
3865
|
+
),
|
|
3866
|
+
/* @__PURE__ */ jsx7("button", { type: "submit", disabled: disabled || !text.trim(), children: "Send" })
|
|
3867
|
+
] })
|
|
3868
|
+
] }) : null,
|
|
3869
|
+
!showText && options.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "human-input-card__unavailable", role: "status", children: "This request can\u2019t be answered here." }) : null
|
|
3870
|
+
]
|
|
3871
|
+
}
|
|
3872
|
+
);
|
|
3873
|
+
}
|
|
3874
|
+
|
|
3875
|
+
// src/react/components/CollectionResultCard/CollectionResultCard.tsx
|
|
3876
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
3877
|
+
function CollectionResultCard({
|
|
3878
|
+
result
|
|
3879
|
+
}) {
|
|
3880
|
+
const empty = result.items.length === 0;
|
|
3881
|
+
return /* @__PURE__ */ jsxs8(
|
|
3882
|
+
"section",
|
|
3883
|
+
{
|
|
3884
|
+
className: `collection-result-card tool-result-card tool-result-card--${result.status}`,
|
|
3885
|
+
"aria-label": result.title,
|
|
3886
|
+
children: [
|
|
3887
|
+
/* @__PURE__ */ jsxs8("div", { className: "tool-result-card__heading", children: [
|
|
3888
|
+
/* @__PURE__ */ jsx8("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
|
|
3889
|
+
/* @__PURE__ */ jsx8("strong", { children: result.title })
|
|
3890
|
+
] }),
|
|
3891
|
+
empty ? /* @__PURE__ */ jsx8("p", { className: "collection-result-card__empty", children: "No matching record for the email you shared." }) : /* @__PURE__ */ jsx8("ul", { className: "collection-result-card__list", children: result.items.map((item) => /* @__PURE__ */ jsxs8("li", { children: [
|
|
3892
|
+
/* @__PURE__ */ jsx8("div", { className: "collection-result-card__item-title", children: item.title }),
|
|
3893
|
+
item.description ? /* @__PURE__ */ jsx8("p", { children: item.description }) : null,
|
|
3894
|
+
item.details?.length ? /* @__PURE__ */ jsx8("dl", { children: item.details.map((detail) => /* @__PURE__ */ jsxs8("div", { children: [
|
|
3895
|
+
/* @__PURE__ */ jsx8("dt", { children: detail.label }),
|
|
3896
|
+
/* @__PURE__ */ jsx8("dd", { children: detail.value })
|
|
3897
|
+
] }, `${detail.label}:${detail.value}`)) }) : null,
|
|
3898
|
+
item.href ? /* @__PURE__ */ jsx8("a", { href: item.href, target: "_blank", rel: "noreferrer", children: "Open record" }) : null
|
|
3899
|
+
] }, item.title)) })
|
|
3900
|
+
]
|
|
3901
|
+
}
|
|
3902
|
+
);
|
|
3903
|
+
}
|
|
3904
|
+
|
|
3905
|
+
// src/react/components/EntityResultCard/EntityResultCard.tsx
|
|
3906
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3907
|
+
function EntityResultCard({
|
|
3908
|
+
result
|
|
3909
|
+
}) {
|
|
3910
|
+
return /* @__PURE__ */ jsxs9(
|
|
3911
|
+
"section",
|
|
3912
|
+
{
|
|
3913
|
+
className: `entity-result-card tool-result-card tool-result-card--${result.status}`,
|
|
3914
|
+
"aria-label": result.title,
|
|
3915
|
+
children: [
|
|
3916
|
+
/* @__PURE__ */ jsxs9("div", { className: "tool-result-card__heading", children: [
|
|
3917
|
+
/* @__PURE__ */ jsx9("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
|
|
3918
|
+
/* @__PURE__ */ jsx9("strong", { children: result.title })
|
|
3919
|
+
] }),
|
|
3920
|
+
result.description ? /* @__PURE__ */ jsx9("p", { children: result.description }) : null,
|
|
3921
|
+
result.details?.length ? /* @__PURE__ */ jsx9("dl", { children: result.details.map((detail) => /* @__PURE__ */ jsxs9("div", { children: [
|
|
3922
|
+
/* @__PURE__ */ jsx9("dt", { children: detail.label }),
|
|
3923
|
+
/* @__PURE__ */ jsx9("dd", { children: detail.value })
|
|
3924
|
+
] }, `${detail.label}:${detail.value}`)) }) : null,
|
|
3925
|
+
result.links?.length ? /* @__PURE__ */ jsx9("div", { className: "tool-result-card__links", children: result.links.map((link) => /* @__PURE__ */ jsx9(
|
|
3926
|
+
"a",
|
|
3927
|
+
{
|
|
3928
|
+
href: link.href,
|
|
3929
|
+
target: "_blank",
|
|
3930
|
+
rel: "noreferrer",
|
|
3931
|
+
children: link.label
|
|
3932
|
+
},
|
|
3933
|
+
link.href
|
|
3934
|
+
)) }) : null
|
|
3935
|
+
]
|
|
3936
|
+
}
|
|
3937
|
+
);
|
|
3938
|
+
}
|
|
3939
|
+
|
|
3940
|
+
// src/react/components/SignatureResultCard/SignatureResultCard.tsx
|
|
3941
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3942
|
+
function SignatureResultCard({
|
|
3943
|
+
result
|
|
3944
|
+
}) {
|
|
3945
|
+
const primaryLink = result.links?.[0];
|
|
3946
|
+
return /* @__PURE__ */ jsxs10(
|
|
3947
|
+
"section",
|
|
3948
|
+
{
|
|
3949
|
+
className: `signature-result-card tool-result-card tool-result-card--${result.status}`,
|
|
3950
|
+
"aria-label": result.title,
|
|
3951
|
+
children: [
|
|
3952
|
+
/* @__PURE__ */ jsxs10("div", { className: "tool-result-card__heading", children: [
|
|
3953
|
+
/* @__PURE__ */ jsx10("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
|
|
3954
|
+
/* @__PURE__ */ jsx10("strong", { children: result.title })
|
|
3955
|
+
] }),
|
|
3956
|
+
result.statusLabel ? /* @__PURE__ */ jsx10("span", { className: "signature-result-card__badge", children: result.statusLabel }) : null,
|
|
3957
|
+
result.description ? /* @__PURE__ */ jsx10("p", { children: result.description }) : null,
|
|
3958
|
+
primaryLink ? /* @__PURE__ */ jsx10(
|
|
3959
|
+
"a",
|
|
3960
|
+
{
|
|
3961
|
+
className: "signature-result-card__cta",
|
|
3962
|
+
href: primaryLink.href,
|
|
3963
|
+
target: "_blank",
|
|
3964
|
+
rel: "noreferrer",
|
|
3965
|
+
children: primaryLink.label
|
|
3966
|
+
}
|
|
3967
|
+
) : null
|
|
3968
|
+
]
|
|
3969
|
+
}
|
|
3970
|
+
);
|
|
3971
|
+
}
|
|
3972
|
+
|
|
3973
|
+
// src/react/components/ToolResultCard/ToolResultCard.tsx
|
|
3974
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3975
|
+
function ToolResultCard({
|
|
3976
|
+
result
|
|
3977
|
+
}) {
|
|
3978
|
+
return /* @__PURE__ */ jsxs11(
|
|
3979
|
+
"section",
|
|
3980
|
+
{
|
|
3981
|
+
className: `tool-result-card tool-result-card--${result.status}`,
|
|
3982
|
+
"aria-label": result.title,
|
|
3983
|
+
children: [
|
|
3984
|
+
/* @__PURE__ */ jsxs11("div", { className: "tool-result-card__heading", children: [
|
|
3985
|
+
/* @__PURE__ */ jsx11("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
|
|
3986
|
+
/* @__PURE__ */ jsx11("strong", { children: result.title })
|
|
3987
|
+
] }),
|
|
3988
|
+
result.description ? /* @__PURE__ */ jsx11("p", { children: result.description }) : null,
|
|
3989
|
+
result.details?.length ? /* @__PURE__ */ jsx11("dl", { children: result.details.map((detail) => /* @__PURE__ */ jsxs11("div", { children: [
|
|
3990
|
+
/* @__PURE__ */ jsx11("dt", { children: detail.label }),
|
|
3991
|
+
/* @__PURE__ */ jsx11("dd", { children: detail.value })
|
|
3992
|
+
] }, `${detail.label}:${detail.value}`)) }) : null,
|
|
3993
|
+
result.links?.length ? /* @__PURE__ */ jsx11("div", { className: "tool-result-card__links", children: result.links.map((link) => /* @__PURE__ */ jsx11(
|
|
3994
|
+
"a",
|
|
3995
|
+
{
|
|
3996
|
+
href: link.href,
|
|
3997
|
+
target: "_blank",
|
|
3998
|
+
rel: "noreferrer",
|
|
3999
|
+
children: link.label
|
|
4000
|
+
},
|
|
4001
|
+
link.href
|
|
4002
|
+
)) }) : null
|
|
4003
|
+
]
|
|
4004
|
+
}
|
|
4005
|
+
);
|
|
4006
|
+
}
|
|
4007
|
+
|
|
4008
|
+
// src/react/components/VisitorToolResultView/VisitorToolResultView.tsx
|
|
4009
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
4010
|
+
function VisitorToolResultView({
|
|
4011
|
+
result
|
|
4012
|
+
}) {
|
|
4013
|
+
if (result.kind === "entity") {
|
|
4014
|
+
return /* @__PURE__ */ jsx12(EntityResultCard, { result });
|
|
4015
|
+
}
|
|
4016
|
+
if (result.kind === "collection") {
|
|
4017
|
+
return /* @__PURE__ */ jsx12(CollectionResultCard, { result });
|
|
4018
|
+
}
|
|
4019
|
+
if (result.kind === "signature") {
|
|
4020
|
+
return /* @__PURE__ */ jsx12(SignatureResultCard, { result });
|
|
4021
|
+
}
|
|
4022
|
+
if (result.kind === "summary") {
|
|
4023
|
+
return /* @__PURE__ */ jsx12(ToolResultCard, { result });
|
|
4024
|
+
}
|
|
4025
|
+
return null;
|
|
4026
|
+
}
|
|
4027
|
+
function isRenderableVisitorToolResult(result) {
|
|
4028
|
+
return result.kind === "summary" || result.kind === "entity" || result.kind === "collection" || result.kind === "signature";
|
|
4029
|
+
}
|
|
4030
|
+
|
|
4031
|
+
// src/react/components/AgentRail/AgentRail.tsx
|
|
4032
|
+
import { Fragment, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2217
4033
|
function MinimizeIcon() {
|
|
2218
|
-
return /* @__PURE__ */
|
|
4034
|
+
return /* @__PURE__ */ jsx13("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
|
|
2219
4035
|
"path",
|
|
2220
4036
|
{
|
|
2221
4037
|
d: "M3.5 8h9",
|
|
@@ -2226,7 +4042,7 @@ function MinimizeIcon() {
|
|
|
2226
4042
|
) });
|
|
2227
4043
|
}
|
|
2228
4044
|
function CloseIcon() {
|
|
2229
|
-
return /* @__PURE__ */
|
|
4045
|
+
return /* @__PURE__ */ jsx13("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
|
|
2230
4046
|
"path",
|
|
2231
4047
|
{
|
|
2232
4048
|
d: "M4 4l8 8M12 4l-8 8",
|
|
@@ -2237,7 +4053,7 @@ function CloseIcon() {
|
|
|
2237
4053
|
) });
|
|
2238
4054
|
}
|
|
2239
4055
|
function NewChatIcon() {
|
|
2240
|
-
return /* @__PURE__ */
|
|
4056
|
+
return /* @__PURE__ */ jsx13("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
|
|
2241
4057
|
"path",
|
|
2242
4058
|
{
|
|
2243
4059
|
d: "M9.5 3.5h3v3M12.25 3.75 8 8M7 4H4.5A1.5 1.5 0 0 0 3 5.5v6A1.5 1.5 0 0 0 4.5 13h6a1.5 1.5 0 0 0 1.5-1.5V9",
|
|
@@ -2249,7 +4065,7 @@ function NewChatIcon() {
|
|
|
2249
4065
|
) });
|
|
2250
4066
|
}
|
|
2251
4067
|
function ExpandIcon() {
|
|
2252
|
-
return /* @__PURE__ */
|
|
4068
|
+
return /* @__PURE__ */ jsx13("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
|
|
2253
4069
|
"path",
|
|
2254
4070
|
{
|
|
2255
4071
|
d: "M6 3.5H3.5V6M10 3.5h2.5V6M10 12.5h2.5V10M6 12.5H3.5V10",
|
|
@@ -2261,7 +4077,7 @@ function ExpandIcon() {
|
|
|
2261
4077
|
) });
|
|
2262
4078
|
}
|
|
2263
4079
|
function RestoreIcon() {
|
|
2264
|
-
return /* @__PURE__ */
|
|
4080
|
+
return /* @__PURE__ */ jsx13("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
|
|
2265
4081
|
"path",
|
|
2266
4082
|
{
|
|
2267
4083
|
d: "M5.5 5.5H3.5V7.5M10.5 5.5h2V7.5M10.5 10.5h2V8.5M5.5 10.5H3.5V8.5",
|
|
@@ -2289,28 +4105,29 @@ function AgentRail({
|
|
|
2289
4105
|
onRetry,
|
|
2290
4106
|
onSubmit,
|
|
2291
4107
|
onFollowUpSelect,
|
|
2292
|
-
onBook
|
|
4108
|
+
onBook,
|
|
4109
|
+
onInputResponse
|
|
2293
4110
|
}) {
|
|
2294
4111
|
const transcriptRef = useRef3(null);
|
|
2295
4112
|
const resolvedBrandLabel = brandLabel.trim();
|
|
2296
4113
|
const resolvedBrandLogoUrl = brandLogoUrl?.trim();
|
|
2297
|
-
const [failedLogoUrl, setFailedLogoUrl] =
|
|
4114
|
+
const [failedLogoUrl, setFailedLogoUrl] = useState7(null);
|
|
2298
4115
|
const showBrandLogo = Boolean(resolvedBrandLogoUrl) && failedLogoUrl !== resolvedBrandLogoUrl;
|
|
2299
4116
|
const resolvedColorScheme = useAgentColorScheme(colorScheme);
|
|
2300
4117
|
const brandedTheme = { ...defaultAgentRailTheme, ...theme };
|
|
2301
4118
|
const resolvedTheme = resolvedColorScheme === "dark" ? {
|
|
2302
4119
|
...brandedTheme,
|
|
2303
4120
|
brand: theme?.brand ?? defaultDarkAgentRailTheme.brand,
|
|
2304
|
-
brandDeep: defaultDarkAgentRailTheme.brandDeep,
|
|
2305
|
-
brandSoft: `color-mix(in srgb, ${theme?.brand ?? defaultDarkAgentRailTheme.brand} 18%, ${defaultDarkAgentRailTheme.surface})`,
|
|
2306
|
-
border: defaultDarkAgentRailTheme.border,
|
|
2307
|
-
danger: defaultDarkAgentRailTheme.danger,
|
|
2308
|
-
success: defaultDarkAgentRailTheme.success,
|
|
2309
|
-
surface: defaultDarkAgentRailTheme.surface,
|
|
2310
|
-
surfaceMuted: defaultDarkAgentRailTheme.surfaceMuted,
|
|
2311
|
-
text: defaultDarkAgentRailTheme.text,
|
|
2312
|
-
textMuted: defaultDarkAgentRailTheme.textMuted,
|
|
2313
|
-
textSubtle: defaultDarkAgentRailTheme.textSubtle,
|
|
4121
|
+
brandDeep: theme?.brandDeep ?? defaultDarkAgentRailTheme.brandDeep,
|
|
4122
|
+
brandSoft: theme?.brandSoft ?? `color-mix(in srgb, ${theme?.brand ?? defaultDarkAgentRailTheme.brand} 18%, ${defaultDarkAgentRailTheme.surface})`,
|
|
4123
|
+
border: theme?.border ?? defaultDarkAgentRailTheme.border,
|
|
4124
|
+
danger: theme?.danger ?? defaultDarkAgentRailTheme.danger,
|
|
4125
|
+
success: theme?.success ?? defaultDarkAgentRailTheme.success,
|
|
4126
|
+
surface: theme?.surface ?? defaultDarkAgentRailTheme.surface,
|
|
4127
|
+
surfaceMuted: theme?.surfaceMuted ?? defaultDarkAgentRailTheme.surfaceMuted,
|
|
4128
|
+
text: theme?.text ?? defaultDarkAgentRailTheme.text,
|
|
4129
|
+
textMuted: theme?.textMuted ?? defaultDarkAgentRailTheme.textMuted,
|
|
4130
|
+
textSubtle: theme?.textSubtle ?? defaultDarkAgentRailTheme.textSubtle,
|
|
2314
4131
|
visitorBubble: theme?.visitorBubble ?? theme?.brand ?? defaultDarkAgentRailTheme.visitorBubble
|
|
2315
4132
|
} : brandedTheme;
|
|
2316
4133
|
const railStyle = {
|
|
@@ -2333,8 +4150,15 @@ function AgentRail({
|
|
|
2333
4150
|
"--as-font-display": resolvedTheme.fontDisplay,
|
|
2334
4151
|
colorScheme: resolvedColorScheme
|
|
2335
4152
|
};
|
|
2336
|
-
const
|
|
4153
|
+
const pendingInputRequests = (state.pendingInputs ?? []).filter(
|
|
4154
|
+
(request) => shouldRenderVisitorInputCard(request) && (!state.pendingOffer || request.kind === "tool-approval")
|
|
4155
|
+
);
|
|
4156
|
+
const isBusy = state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming" || state.phase === "waiting-input" && pendingInputRequests.length > 0;
|
|
4157
|
+
const semanticSurfaceDisabled = isBusy && state.phase !== "waiting-input";
|
|
2337
4158
|
const showActivity = state.toolSteps.length > 0;
|
|
4159
|
+
const visitorToolResults = (state.toolResults ?? []).filter(
|
|
4160
|
+
isRenderableVisitorToolResult
|
|
4161
|
+
);
|
|
2338
4162
|
const hasVisitorMessages2 = state.messages.some(
|
|
2339
4163
|
(message) => message.role === "visitor"
|
|
2340
4164
|
);
|
|
@@ -2344,22 +4168,44 @@ function AgentRail({
|
|
|
2344
4168
|
);
|
|
2345
4169
|
const visibleMessages = hasVisitorMessages2 ? state.messages : [];
|
|
2346
4170
|
const lastMessage = visibleMessages.at(-1);
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
4171
|
+
let lastAgentIndex = -1;
|
|
4172
|
+
for (let index = visibleMessages.length - 1; index >= 0; index -= 1) {
|
|
4173
|
+
if (visibleMessages[index]?.role === "agent") {
|
|
4174
|
+
lastAgentIndex = index;
|
|
4175
|
+
break;
|
|
4176
|
+
}
|
|
4177
|
+
}
|
|
4178
|
+
let lastVisitorIndex = -1;
|
|
4179
|
+
for (let index = visibleMessages.length - 1; index >= 0; index -= 1) {
|
|
4180
|
+
if (visibleMessages[index]?.role === "visitor") {
|
|
4181
|
+
lastVisitorIndex = index;
|
|
4182
|
+
break;
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
const lastIsAgent = lastMessage?.role === "agent";
|
|
4186
|
+
const streamingMessage = state.phase === "streaming" && state.streamingText && !lastIsAgent ? {
|
|
2350
4187
|
createdAt: 0,
|
|
2351
4188
|
id: "streaming-response",
|
|
2352
4189
|
role: "agent",
|
|
2353
4190
|
streaming: true,
|
|
2354
4191
|
text: state.streamingText
|
|
2355
|
-
} : state.pendingOffer ? {
|
|
4192
|
+
} : state.pendingOffer && !lastIsAgent ? {
|
|
2356
4193
|
createdAt: 0,
|
|
2357
4194
|
id: "pending-booking",
|
|
2358
4195
|
role: "agent",
|
|
2359
4196
|
streaming: false,
|
|
2360
4197
|
text: "Pick a date and time that works for you."
|
|
2361
4198
|
} : null;
|
|
2362
|
-
|
|
4199
|
+
const bookingReadyText = state.streamingText || (lastIsAgent && lastMessage?.role === "agent" ? lastMessage.text : "");
|
|
4200
|
+
const waitingForBooking = !state.pendingOffer && looksLikeBookingReady(bookingReadyText) && (state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming");
|
|
4201
|
+
const lastAgentText = lastIsAgent && lastMessage?.role === "agent" ? lastMessage.text : "";
|
|
4202
|
+
const composerForm = resolveComposerForm({
|
|
4203
|
+
agentText: lastAgentText,
|
|
4204
|
+
cards: extractToolCards(lastAgentText),
|
|
4205
|
+
hasBookingOffer: Boolean(state.pendingOffer) || waitingForBooking,
|
|
4206
|
+
enabled: lastIsAgent && !isBusy
|
|
4207
|
+
});
|
|
4208
|
+
useEffect3(() => {
|
|
2363
4209
|
const node = transcriptRef.current;
|
|
2364
4210
|
if (!node) return;
|
|
2365
4211
|
node.scrollTop = node.scrollHeight;
|
|
@@ -2370,11 +4216,13 @@ function AgentRail({
|
|
|
2370
4216
|
state.followUps,
|
|
2371
4217
|
state.journey
|
|
2372
4218
|
]);
|
|
2373
|
-
return /* @__PURE__ */
|
|
4219
|
+
return /* @__PURE__ */ jsxs12(
|
|
2374
4220
|
"aside",
|
|
2375
4221
|
{
|
|
2376
|
-
className: `agent-rail${mobileFullscreen ? " agent-rail--mobile-fullscreen" : ""}${expanded ? " agent-rail--expanded" : ""}`,
|
|
4222
|
+
className: `agent-rail not-typeset${mobileFullscreen ? " agent-rail--mobile-fullscreen" : ""}${expanded ? " agent-rail--expanded" : ""}`,
|
|
4223
|
+
"data-not-typeset": "",
|
|
2377
4224
|
"data-color-scheme": resolvedColorScheme,
|
|
4225
|
+
spellCheck: false,
|
|
2378
4226
|
style: railStyle,
|
|
2379
4227
|
"aria-label": "Agent conversation",
|
|
2380
4228
|
"aria-modal": mobileFullscreen || expanded ? true : void 0,
|
|
@@ -2382,28 +4230,28 @@ function AgentRail({
|
|
|
2382
4230
|
role: mobileFullscreen || expanded ? "dialog" : void 0,
|
|
2383
4231
|
tabIndex: mobileFullscreen || expanded ? -1 : void 0,
|
|
2384
4232
|
children: [
|
|
2385
|
-
/* @__PURE__ */
|
|
2386
|
-
onCollapse ? /* @__PURE__ */
|
|
4233
|
+
/* @__PURE__ */ jsx13("header", { className: "agent-rail__header", children: /* @__PURE__ */ jsxs12("div", { className: "agent-rail__brand-row", children: [
|
|
4234
|
+
onCollapse ? /* @__PURE__ */ jsx13(
|
|
2387
4235
|
"button",
|
|
2388
4236
|
{
|
|
2389
4237
|
type: "button",
|
|
2390
4238
|
className: "agent-rail__collapse",
|
|
2391
4239
|
"aria-label": "Collapse assist",
|
|
2392
4240
|
onClick: onCollapse,
|
|
2393
|
-
children: /* @__PURE__ */
|
|
4241
|
+
children: /* @__PURE__ */ jsx13(MinimizeIcon, {})
|
|
2394
4242
|
}
|
|
2395
|
-
) : onClose ? /* @__PURE__ */
|
|
4243
|
+
) : onClose ? /* @__PURE__ */ jsx13(
|
|
2396
4244
|
"button",
|
|
2397
4245
|
{
|
|
2398
4246
|
type: "button",
|
|
2399
4247
|
className: "agent-rail__close",
|
|
2400
4248
|
"aria-label": "Close agent",
|
|
2401
4249
|
onClick: onClose,
|
|
2402
|
-
children: /* @__PURE__ */
|
|
4250
|
+
children: /* @__PURE__ */ jsx13(CloseIcon, {})
|
|
2403
4251
|
}
|
|
2404
|
-
) : /* @__PURE__ */
|
|
2405
|
-
resolvedBrandLabel || showBrandLogo ? /* @__PURE__ */
|
|
2406
|
-
showBrandLogo ? /* @__PURE__ */
|
|
4252
|
+
) : /* @__PURE__ */ jsx13("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
|
|
4253
|
+
resolvedBrandLabel || showBrandLogo ? /* @__PURE__ */ jsxs12("span", { className: "agent-rail__identity", children: [
|
|
4254
|
+
showBrandLogo ? /* @__PURE__ */ jsx13("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
|
|
2407
4255
|
"img",
|
|
2408
4256
|
{
|
|
2409
4257
|
className: "agent-rail__brand-logo",
|
|
@@ -2414,10 +4262,10 @@ function AgentRail({
|
|
|
2414
4262
|
}
|
|
2415
4263
|
}
|
|
2416
4264
|
) }) : null,
|
|
2417
|
-
resolvedBrandLabel ? /* @__PURE__ */
|
|
4265
|
+
resolvedBrandLabel ? /* @__PURE__ */ jsx13("span", { className: "agent-rail__brand-label", children: resolvedBrandLabel }) : null
|
|
2418
4266
|
] }) : null,
|
|
2419
|
-
/* @__PURE__ */
|
|
2420
|
-
onReset ? /* @__PURE__ */
|
|
4267
|
+
/* @__PURE__ */ jsxs12("span", { className: "agent-rail__actions", children: [
|
|
4268
|
+
onReset ? /* @__PURE__ */ jsx13(
|
|
2421
4269
|
"button",
|
|
2422
4270
|
{
|
|
2423
4271
|
type: "button",
|
|
@@ -2425,24 +4273,24 @@ function AgentRail({
|
|
|
2425
4273
|
"aria-label": "Start a new conversation",
|
|
2426
4274
|
disabled: !hasVisitorMessages2,
|
|
2427
4275
|
onClick: onReset,
|
|
2428
|
-
children: /* @__PURE__ */
|
|
4276
|
+
children: /* @__PURE__ */ jsx13(NewChatIcon, {})
|
|
2429
4277
|
}
|
|
2430
4278
|
) : null,
|
|
2431
|
-
onExpandToggle ? /* @__PURE__ */
|
|
4279
|
+
onExpandToggle ? /* @__PURE__ */ jsx13(
|
|
2432
4280
|
"button",
|
|
2433
4281
|
{
|
|
2434
4282
|
type: "button",
|
|
2435
4283
|
className: "agent-rail__expand",
|
|
2436
4284
|
"aria-label": expanded ? "Exit full screen" : "Open full screen",
|
|
2437
4285
|
onClick: onExpandToggle,
|
|
2438
|
-
children: expanded ? /* @__PURE__ */
|
|
4286
|
+
children: expanded ? /* @__PURE__ */ jsx13(RestoreIcon, {}) : /* @__PURE__ */ jsx13(ExpandIcon, {})
|
|
2439
4287
|
}
|
|
2440
4288
|
) : null
|
|
2441
4289
|
] })
|
|
2442
4290
|
] }) }),
|
|
2443
|
-
/* @__PURE__ */
|
|
2444
|
-
!hasVisitorMessages2 ? /* @__PURE__ */
|
|
2445
|
-
greeting?.role === "agent" ? /* @__PURE__ */
|
|
4291
|
+
/* @__PURE__ */ jsx13("div", { ref: transcriptRef, className: "agent-rail__transcript", children: /* @__PURE__ */ jsxs12("div", { className: "agent-rail__thread", children: [
|
|
4292
|
+
!hasVisitorMessages2 ? /* @__PURE__ */ jsxs12("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
|
|
4293
|
+
greeting?.role === "agent" ? /* @__PURE__ */ jsx13(
|
|
2446
4294
|
MessageBubble,
|
|
2447
4295
|
{
|
|
2448
4296
|
message: greeting,
|
|
@@ -2450,7 +4298,7 @@ function AgentRail({
|
|
|
2450
4298
|
onBook
|
|
2451
4299
|
}
|
|
2452
4300
|
) : null,
|
|
2453
|
-
showIdleFollowUps ? /* @__PURE__ */
|
|
4301
|
+
showIdleFollowUps ? /* @__PURE__ */ jsx13("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ jsx13(
|
|
2454
4302
|
FollowUpChips,
|
|
2455
4303
|
{
|
|
2456
4304
|
suggestions: state.followUps,
|
|
@@ -2458,64 +4306,94 @@ function AgentRail({
|
|
|
2458
4306
|
label: "Start here",
|
|
2459
4307
|
onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
|
|
2460
4308
|
}
|
|
2461
|
-
) }) : null
|
|
4309
|
+
) }) : null,
|
|
4310
|
+
showActivity ? /* @__PURE__ */ jsx13(
|
|
4311
|
+
AgentActivityBubble,
|
|
4312
|
+
{
|
|
4313
|
+
brandLabel: resolvedBrandLabel,
|
|
4314
|
+
brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
|
|
4315
|
+
failed: state.phase === "error",
|
|
4316
|
+
steps: state.toolSteps
|
|
4317
|
+
}
|
|
4318
|
+
) : null,
|
|
4319
|
+
visitorToolResults.map((result) => /* @__PURE__ */ jsx13(VisitorToolResultView, { result }, result.id)),
|
|
4320
|
+
pendingInputRequests.map((request) => /* @__PURE__ */ jsx13(
|
|
4321
|
+
HumanInputCard,
|
|
4322
|
+
{
|
|
4323
|
+
request,
|
|
4324
|
+
onRespond: onInputResponse
|
|
4325
|
+
},
|
|
4326
|
+
request.requestId
|
|
4327
|
+
))
|
|
2462
4328
|
] }) : null,
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
4329
|
+
visibleMessages.map((message, index) => /* @__PURE__ */ jsxs12("div", { className: "agent-rail__turn-block", children: [
|
|
4330
|
+
/* @__PURE__ */ jsx13(
|
|
4331
|
+
MessageBubble,
|
|
4332
|
+
{
|
|
4333
|
+
message,
|
|
4334
|
+
brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
|
|
4335
|
+
offer: index === lastAgentIndex ? state.pendingOffer : void 0,
|
|
4336
|
+
onBook
|
|
4337
|
+
}
|
|
4338
|
+
),
|
|
4339
|
+
index === lastVisitorIndex ? /* @__PURE__ */ jsxs12(Fragment, { children: [
|
|
4340
|
+
showActivity ? /* @__PURE__ */ jsx13(
|
|
4341
|
+
AgentActivityBubble,
|
|
4342
|
+
{
|
|
4343
|
+
brandLabel: resolvedBrandLabel,
|
|
4344
|
+
brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
|
|
4345
|
+
failed: state.phase === "error",
|
|
4346
|
+
steps: state.toolSteps
|
|
4347
|
+
}
|
|
4348
|
+
) : null,
|
|
4349
|
+
visitorToolResults.map((result) => /* @__PURE__ */ jsx13(VisitorToolResultView, { result }, result.id)),
|
|
4350
|
+
pendingInputRequests.map((request) => /* @__PURE__ */ jsx13(
|
|
4351
|
+
HumanInputCard,
|
|
4352
|
+
{
|
|
4353
|
+
request,
|
|
4354
|
+
onRespond: onInputResponse
|
|
4355
|
+
},
|
|
4356
|
+
request.requestId
|
|
4357
|
+
))
|
|
4358
|
+
] }) : null
|
|
4359
|
+
] }, message.id)),
|
|
4360
|
+
streamingMessage ? /* @__PURE__ */ jsx13(
|
|
2482
4361
|
MessageBubble,
|
|
2483
4362
|
{
|
|
2484
|
-
message:
|
|
4363
|
+
message: streamingMessage,
|
|
2485
4364
|
brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
|
|
4365
|
+
offer: state.pendingOffer,
|
|
2486
4366
|
onBook
|
|
2487
4367
|
}
|
|
2488
4368
|
) : null,
|
|
2489
|
-
|
|
2490
|
-
|
|
4369
|
+
waitingForBooking ? /* @__PURE__ */ jsx13(
|
|
4370
|
+
BookingCard,
|
|
2491
4371
|
{
|
|
2492
|
-
|
|
2493
|
-
brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
|
|
2494
|
-
offer: state.pendingOffer,
|
|
2495
|
-
onBook
|
|
4372
|
+
offer: { type: "booking_offer", eventTypes: [], slots: [] }
|
|
2496
4373
|
}
|
|
2497
4374
|
) : null,
|
|
2498
|
-
state.error ? /* @__PURE__ */
|
|
2499
|
-
/* @__PURE__ */
|
|
2500
|
-
/* @__PURE__ */
|
|
2501
|
-
/* @__PURE__ */
|
|
4375
|
+
state.error ? /* @__PURE__ */ jsxs12("section", { className: "agent-rail__error", role: "alert", children: [
|
|
4376
|
+
/* @__PURE__ */ jsxs12("div", { children: [
|
|
4377
|
+
/* @__PURE__ */ jsx13("strong", { children: "Something went wrong" }),
|
|
4378
|
+
/* @__PURE__ */ jsx13("p", { children: state.error })
|
|
2502
4379
|
] }),
|
|
2503
|
-
onRetry ? /* @__PURE__ */
|
|
4380
|
+
onRetry ? /* @__PURE__ */ jsx13("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
|
|
2504
4381
|
] }) : null
|
|
2505
4382
|
] }) }),
|
|
2506
|
-
/* @__PURE__ */
|
|
2507
|
-
/* @__PURE__ */
|
|
4383
|
+
/* @__PURE__ */ jsxs12("div", { className: "agent-rail__composer-wrap", children: [
|
|
4384
|
+
/* @__PURE__ */ jsx13(
|
|
2508
4385
|
Composer,
|
|
2509
4386
|
{
|
|
2510
4387
|
variant: expanded || mobileFullscreen ? "dock" : "default",
|
|
2511
4388
|
disabled: isBusy,
|
|
4389
|
+
form: composerForm,
|
|
2512
4390
|
placeholder: composerPlaceholder,
|
|
2513
4391
|
onSubmit
|
|
2514
4392
|
}
|
|
2515
4393
|
),
|
|
2516
|
-
/* @__PURE__ */
|
|
2517
|
-
/* @__PURE__ */
|
|
2518
|
-
/* @__PURE__ */
|
|
4394
|
+
/* @__PURE__ */ jsx13("div", { className: "agent-rail__footer", children: /* @__PURE__ */ jsxs12("p", { children: [
|
|
4395
|
+
/* @__PURE__ */ jsx13("span", { children: "AI can make mistakes. Check important info." }),
|
|
4396
|
+
/* @__PURE__ */ jsx13("span", { children: poweredByLabel })
|
|
2519
4397
|
] }) })
|
|
2520
4398
|
] })
|
|
2521
4399
|
]
|
|
@@ -2524,9 +4402,9 @@ function AgentRail({
|
|
|
2524
4402
|
}
|
|
2525
4403
|
|
|
2526
4404
|
// src/react/components/AssistEdgeTab/AssistEdgeTab.tsx
|
|
2527
|
-
import { Fragment as Fragment2, jsx as
|
|
4405
|
+
import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2528
4406
|
function SparklesIcon() {
|
|
2529
|
-
return /* @__PURE__ */
|
|
4407
|
+
return /* @__PURE__ */ jsxs13(
|
|
2530
4408
|
"svg",
|
|
2531
4409
|
{
|
|
2532
4410
|
className: "assist-edge-tab__sparkles",
|
|
@@ -2534,21 +4412,21 @@ function SparklesIcon() {
|
|
|
2534
4412
|
fill: "none",
|
|
2535
4413
|
"aria-hidden": "true",
|
|
2536
4414
|
children: [
|
|
2537
|
-
/* @__PURE__ */
|
|
4415
|
+
/* @__PURE__ */ jsx14(
|
|
2538
4416
|
"path",
|
|
2539
4417
|
{
|
|
2540
4418
|
d: "M8 1.2l.95 2.7 2.85.05-2.25 1.75.8 2.75L8 6.7 5.65 8.45l.8-2.75L4.2 3.95l2.85-.05L8 1.2z",
|
|
2541
4419
|
fill: "currentColor"
|
|
2542
4420
|
}
|
|
2543
4421
|
),
|
|
2544
|
-
/* @__PURE__ */
|
|
4422
|
+
/* @__PURE__ */ jsx14(
|
|
2545
4423
|
"path",
|
|
2546
4424
|
{
|
|
2547
4425
|
d: "M14.2 6.4l.55 1.55 1.65.03-1.3 1 .46 1.58-1.36-1-1.36 1 .46-1.58-1.3-1 1.65-.03.55-1.55z",
|
|
2548
4426
|
fill: "currentColor"
|
|
2549
4427
|
}
|
|
2550
4428
|
),
|
|
2551
|
-
/* @__PURE__ */
|
|
4429
|
+
/* @__PURE__ */ jsx14(
|
|
2552
4430
|
"path",
|
|
2553
4431
|
{
|
|
2554
4432
|
d: "M3.1 9.1l.4 1.15 1.22.02-.96.74.34 1.17-1-.74-1 .74.34-1.17-.96-.74 1.22-.02.4-1.15z",
|
|
@@ -2562,7 +4440,7 @@ function SparklesIcon() {
|
|
|
2562
4440
|
function TabMarkIcon({ customIconUrl }) {
|
|
2563
4441
|
const url = customIconUrl?.trim();
|
|
2564
4442
|
if (url) {
|
|
2565
|
-
return /* @__PURE__ */
|
|
4443
|
+
return /* @__PURE__ */ jsx14(
|
|
2566
4444
|
"img",
|
|
2567
4445
|
{
|
|
2568
4446
|
alt: "",
|
|
@@ -2572,10 +4450,10 @@ function TabMarkIcon({ customIconUrl }) {
|
|
|
2572
4450
|
}
|
|
2573
4451
|
);
|
|
2574
4452
|
}
|
|
2575
|
-
return /* @__PURE__ */
|
|
4453
|
+
return /* @__PURE__ */ jsx14(SparklesIcon, {});
|
|
2576
4454
|
}
|
|
2577
4455
|
function ChevronLeftIcon() {
|
|
2578
|
-
return /* @__PURE__ */
|
|
4456
|
+
return /* @__PURE__ */ jsx14("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx14(
|
|
2579
4457
|
"path",
|
|
2580
4458
|
{
|
|
2581
4459
|
d: "M10 4L6 8l4 4",
|
|
@@ -2587,7 +4465,7 @@ function ChevronLeftIcon() {
|
|
|
2587
4465
|
) });
|
|
2588
4466
|
}
|
|
2589
4467
|
function ChevronDownIcon() {
|
|
2590
|
-
return /* @__PURE__ */
|
|
4468
|
+
return /* @__PURE__ */ jsx14("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx14(
|
|
2591
4469
|
"path",
|
|
2592
4470
|
{
|
|
2593
4471
|
d: "M4 6l4 4 4-4",
|
|
@@ -2599,7 +4477,7 @@ function ChevronDownIcon() {
|
|
|
2599
4477
|
) });
|
|
2600
4478
|
}
|
|
2601
4479
|
function DragDots() {
|
|
2602
|
-
return /* @__PURE__ */
|
|
4480
|
+
return /* @__PURE__ */ jsx14("span", { className: "assist-edge-tab__dots", "aria-hidden": "true", children: Array.from({ length: 12 }, (_, index) => /* @__PURE__ */ jsx14("i", {}, index)) });
|
|
2603
4481
|
}
|
|
2604
4482
|
var VARIANT_COPY = {
|
|
2605
4483
|
outline: { label: "Ask anything", aria: "Ask anything" },
|
|
@@ -2628,6 +4506,7 @@ function AssistEdgeTab({
|
|
|
2628
4506
|
const resolvedColorScheme = useAgentColorScheme(colorScheme);
|
|
2629
4507
|
const copy = VARIANT_COPY[variant];
|
|
2630
4508
|
const visibleLabel = label?.trim() || copy.label;
|
|
4509
|
+
const alignment = along < 50 ? "start" : along > 50 ? "end" : "center";
|
|
2631
4510
|
const showLogo = Boolean(logoUrl?.trim()) && !customIconUrl?.trim();
|
|
2632
4511
|
const resolvedBrandColor = brandColor ?? (resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.brand : void 0);
|
|
2633
4512
|
const resolvedBorderColor = resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.border : borderColor;
|
|
@@ -2644,11 +4523,11 @@ function AssistEdgeTab({
|
|
|
2644
4523
|
...resolvedTextColor ? { "--as-text": resolvedTextColor } : {},
|
|
2645
4524
|
colorScheme: resolvedColorScheme
|
|
2646
4525
|
};
|
|
2647
|
-
return /* @__PURE__ */
|
|
4526
|
+
return /* @__PURE__ */ jsxs13(
|
|
2648
4527
|
"button",
|
|
2649
4528
|
{
|
|
2650
4529
|
type: "button",
|
|
2651
|
-
className: `assist-edge-tab assist-edge-tab--${variant} assist-edge-tab--${side}${mobile ? " assist-edge-tab--mobile" : ""}${visible ? " is-visible" : ""}`,
|
|
4530
|
+
className: `assist-edge-tab assist-edge-tab--${variant} assist-edge-tab--${side} assist-edge-tab--align-${alignment}${mobile ? " assist-edge-tab--mobile" : ""}${visible ? " is-visible" : ""}`,
|
|
2652
4531
|
"data-color-scheme": resolvedColorScheme,
|
|
2653
4532
|
style,
|
|
2654
4533
|
"aria-label": `Open ${visibleLabel}`,
|
|
@@ -2656,15 +4535,15 @@ function AssistEdgeTab({
|
|
|
2656
4535
|
tabIndex: visible ? 0 : -1,
|
|
2657
4536
|
onClick: onOpen,
|
|
2658
4537
|
children: [
|
|
2659
|
-
mobile ? /* @__PURE__ */
|
|
2660
|
-
/* @__PURE__ */
|
|
4538
|
+
mobile ? /* @__PURE__ */ jsxs13(Fragment2, { children: [
|
|
4539
|
+
/* @__PURE__ */ jsxs13(
|
|
2661
4540
|
"span",
|
|
2662
4541
|
{
|
|
2663
4542
|
className: "assist-edge-tab__mark assist-edge-tab__mark--mobile",
|
|
2664
4543
|
"aria-hidden": "true",
|
|
2665
4544
|
children: [
|
|
2666
|
-
/* @__PURE__ */
|
|
2667
|
-
showLogo ? /* @__PURE__ */
|
|
4545
|
+
/* @__PURE__ */ jsx14(TabMarkIcon, { customIconUrl }),
|
|
4546
|
+
showLogo ? /* @__PURE__ */ jsx14(
|
|
2668
4547
|
"img",
|
|
2669
4548
|
{
|
|
2670
4549
|
className: "assist-edge-tab__logo",
|
|
@@ -2678,11 +4557,11 @@ function AssistEdgeTab({
|
|
|
2678
4557
|
]
|
|
2679
4558
|
}
|
|
2680
4559
|
),
|
|
2681
|
-
/* @__PURE__ */
|
|
2682
|
-
] }) : variant === "outline" ? /* @__PURE__ */
|
|
2683
|
-
/* @__PURE__ */
|
|
2684
|
-
/* @__PURE__ */
|
|
2685
|
-
showLogo ? /* @__PURE__ */
|
|
4560
|
+
/* @__PURE__ */ jsx14("span", { className: "assist-edge-tab__label", children: visibleLabel })
|
|
4561
|
+
] }) : variant === "outline" ? /* @__PURE__ */ jsxs13(Fragment2, { children: [
|
|
4562
|
+
/* @__PURE__ */ jsxs13("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
|
|
4563
|
+
/* @__PURE__ */ jsx14(TabMarkIcon, { customIconUrl }),
|
|
4564
|
+
showLogo ? /* @__PURE__ */ jsx14(
|
|
2686
4565
|
"img",
|
|
2687
4566
|
{
|
|
2688
4567
|
className: "assist-edge-tab__logo",
|
|
@@ -2694,18 +4573,18 @@ function AssistEdgeTab({
|
|
|
2694
4573
|
}
|
|
2695
4574
|
) : null
|
|
2696
4575
|
] }),
|
|
2697
|
-
/* @__PURE__ */
|
|
2698
|
-
/* @__PURE__ */
|
|
4576
|
+
/* @__PURE__ */ jsx14("span", { className: "assist-edge-tab__label", children: visibleLabel }),
|
|
4577
|
+
/* @__PURE__ */ jsx14(ChevronDownIcon, {})
|
|
2699
4578
|
] }) : null,
|
|
2700
|
-
variant === "ask" ? /* @__PURE__ */
|
|
2701
|
-
/* @__PURE__ */
|
|
2702
|
-
/* @__PURE__ */
|
|
2703
|
-
/* @__PURE__ */
|
|
4579
|
+
variant === "ask" ? /* @__PURE__ */ jsxs13(Fragment2, { children: [
|
|
4580
|
+
/* @__PURE__ */ jsx14(ChevronLeftIcon, {}),
|
|
4581
|
+
/* @__PURE__ */ jsx14("span", { className: "assist-edge-tab__label", children: visibleLabel }),
|
|
4582
|
+
/* @__PURE__ */ jsx14(DragDots, {})
|
|
2704
4583
|
] }) : null,
|
|
2705
|
-
variant === "fill" ? /* @__PURE__ */
|
|
2706
|
-
/* @__PURE__ */
|
|
2707
|
-
/* @__PURE__ */
|
|
2708
|
-
showLogo ? /* @__PURE__ */
|
|
4584
|
+
variant === "fill" ? /* @__PURE__ */ jsxs13(Fragment2, { children: [
|
|
4585
|
+
/* @__PURE__ */ jsxs13("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
|
|
4586
|
+
/* @__PURE__ */ jsx14(TabMarkIcon, { customIconUrl }),
|
|
4587
|
+
showLogo ? /* @__PURE__ */ jsx14(
|
|
2709
4588
|
"img",
|
|
2710
4589
|
{
|
|
2711
4590
|
className: "assist-edge-tab__logo",
|
|
@@ -2717,8 +4596,8 @@ function AssistEdgeTab({
|
|
|
2717
4596
|
}
|
|
2718
4597
|
) : null
|
|
2719
4598
|
] }),
|
|
2720
|
-
/* @__PURE__ */
|
|
2721
|
-
/* @__PURE__ */
|
|
4599
|
+
/* @__PURE__ */ jsx14("span", { className: "assist-edge-tab__label", children: visibleLabel }),
|
|
4600
|
+
/* @__PURE__ */ jsx14(ChevronLeftIcon, {})
|
|
2722
4601
|
] }) : null
|
|
2723
4602
|
]
|
|
2724
4603
|
}
|
|
@@ -2726,10 +4605,10 @@ function AssistEdgeTab({
|
|
|
2726
4605
|
}
|
|
2727
4606
|
|
|
2728
4607
|
// src/react/components/AgentWidget/AgentWidget.tsx
|
|
2729
|
-
import { useEffect as
|
|
4608
|
+
import { useEffect as useEffect6, useRef as useRef4, useState as useState9 } from "react";
|
|
2730
4609
|
|
|
2731
4610
|
// src/react/page-shift.ts
|
|
2732
|
-
import { useEffect as
|
|
4611
|
+
import { useEffect as useEffect4 } from "react";
|
|
2733
4612
|
var PAGE_SHIFT_CLASS = "webless-agent-page-shift";
|
|
2734
4613
|
var DEFAULT_RAIL_WIDTH_PX = 450;
|
|
2735
4614
|
function shouldApplyPageShift(input) {
|
|
@@ -2781,7 +4660,7 @@ function clearPageMargin() {
|
|
|
2781
4660
|
}
|
|
2782
4661
|
function usePageShift(input) {
|
|
2783
4662
|
const { active, railSlotRef } = input;
|
|
2784
|
-
|
|
4663
|
+
useEffect4(() => {
|
|
2785
4664
|
if (typeof document === "undefined") {
|
|
2786
4665
|
return;
|
|
2787
4666
|
}
|
|
@@ -2809,12 +4688,12 @@ function usePageShift(input) {
|
|
|
2809
4688
|
}
|
|
2810
4689
|
|
|
2811
4690
|
// src/react/hooks/useIsMobile.ts
|
|
2812
|
-
import { useEffect as
|
|
4691
|
+
import { useEffect as useEffect5, useState as useState8 } from "react";
|
|
2813
4692
|
function useIsMobile(breakpoint = 767) {
|
|
2814
|
-
const [isMobile, setIsMobile] =
|
|
4693
|
+
const [isMobile, setIsMobile] = useState8(
|
|
2815
4694
|
() => typeof window !== "undefined" && window.matchMedia(`(max-width: ${breakpoint}px)`).matches
|
|
2816
4695
|
);
|
|
2817
|
-
|
|
4696
|
+
useEffect5(() => {
|
|
2818
4697
|
const media = window.matchMedia(`(max-width: ${breakpoint}px)`);
|
|
2819
4698
|
const onChange = () => setIsMobile(media.matches);
|
|
2820
4699
|
onChange();
|
|
@@ -2824,23 +4703,8 @@ function useIsMobile(breakpoint = 767) {
|
|
|
2824
4703
|
return isMobile;
|
|
2825
4704
|
}
|
|
2826
4705
|
|
|
2827
|
-
// src/react/panel-controller.ts
|
|
2828
|
-
var controllers = /* @__PURE__ */ new Map();
|
|
2829
|
-
function registerAgentPanelController(customerId, controller) {
|
|
2830
|
-
controllers.set(customerId, controller);
|
|
2831
|
-
}
|
|
2832
|
-
function unregisterAgentPanelController(customerId) {
|
|
2833
|
-
controllers.delete(customerId);
|
|
2834
|
-
}
|
|
2835
|
-
function openAgentPanel(customerId) {
|
|
2836
|
-
controllers.get(customerId)?.open();
|
|
2837
|
-
}
|
|
2838
|
-
function closeAgentPanel(customerId) {
|
|
2839
|
-
controllers.get(customerId)?.close();
|
|
2840
|
-
}
|
|
2841
|
-
|
|
2842
4706
|
// src/react/components/AgentWidget/AgentWidget.tsx
|
|
2843
|
-
import { jsx as
|
|
4707
|
+
import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2844
4708
|
function AgentWidget({
|
|
2845
4709
|
indexId,
|
|
2846
4710
|
customerId,
|
|
@@ -2853,13 +4717,14 @@ function AgentWidget({
|
|
|
2853
4717
|
pageShift = true,
|
|
2854
4718
|
registerPanelController = false,
|
|
2855
4719
|
colorScheme = "auto",
|
|
2856
|
-
branding
|
|
4720
|
+
branding,
|
|
4721
|
+
toolResultRegistry
|
|
2857
4722
|
}) {
|
|
2858
4723
|
const isMobile = useIsMobile();
|
|
2859
4724
|
const placement = normalizeAgentPlacement(placementInput);
|
|
2860
4725
|
const railSlotRef = useRef4(null);
|
|
2861
|
-
const [railCollapsed, setRailCollapsed] =
|
|
2862
|
-
const [railExpanded, setRailExpanded] =
|
|
4726
|
+
const [railCollapsed, setRailCollapsed] = useState9(defaultCollapsed);
|
|
4727
|
+
const [railExpanded, setRailExpanded] = useState9(false);
|
|
2863
4728
|
const pageShiftActive = shouldApplyPageShift({
|
|
2864
4729
|
pageShift,
|
|
2865
4730
|
isMobile,
|
|
@@ -2870,14 +4735,15 @@ function AgentWidget({
|
|
|
2870
4735
|
active: pageShiftActive,
|
|
2871
4736
|
railSlotRef
|
|
2872
4737
|
});
|
|
2873
|
-
const { state, reset, retry, submit } = useAgentChat({
|
|
4738
|
+
const { state, reset, retry, respondToInput, respondToToolInput, submit } = useAgentChat({
|
|
2874
4739
|
customerId,
|
|
2875
4740
|
getUnpublishedPreviewGrant,
|
|
2876
4741
|
indexId,
|
|
2877
4742
|
previewBuildId,
|
|
2878
4743
|
version,
|
|
2879
4744
|
runtimeOrigin,
|
|
2880
|
-
greeting: branding?.greeting
|
|
4745
|
+
greeting: branding?.greeting,
|
|
4746
|
+
toolResultRegistry
|
|
2881
4747
|
});
|
|
2882
4748
|
const agentName = branding?.agentName ?? "";
|
|
2883
4749
|
const tabLabel = branding?.tabLabel ?? agentName;
|
|
@@ -2898,22 +4764,24 @@ function AgentWidget({
|
|
|
2898
4764
|
} : {},
|
|
2899
4765
|
...branding?.colors?.border ? { border: branding.colors.border } : {}
|
|
2900
4766
|
};
|
|
2901
|
-
|
|
4767
|
+
useEffect6(() => {
|
|
2902
4768
|
if (!registerPanelController) return;
|
|
2903
4769
|
registerAgentPanelController(customerId, {
|
|
2904
4770
|
open: () => setRailCollapsed(false),
|
|
2905
4771
|
close: () => {
|
|
2906
4772
|
setRailCollapsed(true);
|
|
2907
4773
|
setRailExpanded(false);
|
|
2908
|
-
}
|
|
4774
|
+
},
|
|
4775
|
+
reset,
|
|
4776
|
+
submit
|
|
2909
4777
|
});
|
|
2910
4778
|
return () => unregisterAgentPanelController(customerId);
|
|
2911
|
-
}, [customerId, registerPanelController]);
|
|
4779
|
+
}, [customerId, registerPanelController, reset, submit]);
|
|
2912
4780
|
async function handleSubmit(message) {
|
|
2913
4781
|
if (isMobile) setRailCollapsed(false);
|
|
2914
4782
|
await submit(message);
|
|
2915
4783
|
}
|
|
2916
|
-
|
|
4784
|
+
useEffect6(() => {
|
|
2917
4785
|
if (railCollapsed) return;
|
|
2918
4786
|
const handleKeyDown = (event) => {
|
|
2919
4787
|
if (event.key === "Tab" && (isMobile || railExpanded)) {
|
|
@@ -2947,19 +4815,19 @@ function AgentWidget({
|
|
|
2947
4815
|
window.addEventListener("keydown", handleKeyDown);
|
|
2948
4816
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
2949
4817
|
}, [isMobile, railCollapsed, railExpanded]);
|
|
2950
|
-
return /* @__PURE__ */
|
|
2951
|
-
/* @__PURE__ */
|
|
4818
|
+
return /* @__PURE__ */ jsxs14("div", { className: "webless-agent-root", children: [
|
|
4819
|
+
/* @__PURE__ */ jsx15(
|
|
2952
4820
|
"div",
|
|
2953
4821
|
{
|
|
2954
4822
|
className: `webless-agent-root__shell${railCollapsed ? " webless-agent-root__shell--collapsed" : ""}${railExpanded ? " webless-agent-root__shell--expanded" : ""}`,
|
|
2955
|
-
children: /* @__PURE__ */
|
|
4823
|
+
children: /* @__PURE__ */ jsx15(
|
|
2956
4824
|
"div",
|
|
2957
4825
|
{
|
|
2958
4826
|
ref: railSlotRef,
|
|
2959
4827
|
className: "webless-agent-root__rail-slot",
|
|
2960
4828
|
inert: railCollapsed || void 0,
|
|
2961
4829
|
"aria-hidden": railCollapsed,
|
|
2962
|
-
children: /* @__PURE__ */
|
|
4830
|
+
children: /* @__PURE__ */ jsx15(
|
|
2963
4831
|
AgentRail,
|
|
2964
4832
|
{
|
|
2965
4833
|
theme,
|
|
@@ -2977,6 +4845,8 @@ function AgentWidget({
|
|
|
2977
4845
|
onSubmit: handleSubmit,
|
|
2978
4846
|
onReset: reset,
|
|
2979
4847
|
onRetry: () => void retry(),
|
|
4848
|
+
onInputResponse: (response) => void respondToInput(response),
|
|
4849
|
+
onToolInput: (surface, values) => void respondToToolInput(surface, values),
|
|
2980
4850
|
onFollowUpSelect: (label) => void handleSubmit(label),
|
|
2981
4851
|
onBook: (input) => void submit(input.displayText, { runtimeText: input.runtimeText })
|
|
2982
4852
|
}
|
|
@@ -2985,7 +4855,7 @@ function AgentWidget({
|
|
|
2985
4855
|
)
|
|
2986
4856
|
}
|
|
2987
4857
|
),
|
|
2988
|
-
railCollapsed ? /* @__PURE__ */
|
|
4858
|
+
railCollapsed ? /* @__PURE__ */ jsx15(
|
|
2989
4859
|
AssistEdgeTab,
|
|
2990
4860
|
{
|
|
2991
4861
|
variant: placement.variant,
|
|
@@ -3013,6 +4883,8 @@ function AgentWidget({
|
|
|
3013
4883
|
export {
|
|
3014
4884
|
DEFAULT_RUNTIME_ORIGIN,
|
|
3015
4885
|
resolveAgentRuntimeConfig,
|
|
4886
|
+
builtInVisitorToolResultRegistry,
|
|
4887
|
+
presentVisitorToolResult,
|
|
3016
4888
|
useAgentChat,
|
|
3017
4889
|
hasVisitorMessages,
|
|
3018
4890
|
createIdleSuggestions,
|
|
@@ -3021,10 +4893,12 @@ export {
|
|
|
3021
4893
|
normalizeAgentPlacement,
|
|
3022
4894
|
openAgentPanel,
|
|
3023
4895
|
closeAgentPanel,
|
|
4896
|
+
resetAgentPanel,
|
|
4897
|
+
submitAgentPanel,
|
|
3024
4898
|
defaultAgentRailTheme,
|
|
3025
4899
|
defaultDarkAgentRailTheme,
|
|
3026
4900
|
AgentRail,
|
|
3027
4901
|
AssistEdgeTab,
|
|
3028
4902
|
AgentWidget
|
|
3029
4903
|
};
|
|
3030
|
-
//# sourceMappingURL=chunk-
|
|
4904
|
+
//# sourceMappingURL=chunk-U2TIUZJM.js.map
|