dooers-agents-client 0.2.7 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.cjs +342 -11
- package/dist/main.cjs.map +1 -1
- package/dist/main.d.cts +197 -2
- package/dist/main.d.ts +197 -2
- package/dist/main.js +338 -12
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -68,6 +68,82 @@ function toDisplayContentPart(w) {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
function toFormElement(w) {
|
|
72
|
+
const base = {
|
|
73
|
+
name: w.name,
|
|
74
|
+
label: w.label,
|
|
75
|
+
order: w.order ?? 0,
|
|
76
|
+
required: w.required ?? false,
|
|
77
|
+
disabled: w.disabled ?? false
|
|
78
|
+
};
|
|
79
|
+
switch (w.type) {
|
|
80
|
+
case "text_input":
|
|
81
|
+
return {
|
|
82
|
+
...base,
|
|
83
|
+
type: "text_input",
|
|
84
|
+
placeholder: w.placeholder ?? null,
|
|
85
|
+
default: w.default ?? null,
|
|
86
|
+
inputType: w.input_type ?? "text"
|
|
87
|
+
};
|
|
88
|
+
case "textarea_input":
|
|
89
|
+
return {
|
|
90
|
+
...base,
|
|
91
|
+
type: "textarea_input",
|
|
92
|
+
placeholder: w.placeholder ?? null,
|
|
93
|
+
default: w.default ?? null,
|
|
94
|
+
rows: w.rows ?? null
|
|
95
|
+
};
|
|
96
|
+
case "select_input":
|
|
97
|
+
return {
|
|
98
|
+
...base,
|
|
99
|
+
type: "select_input",
|
|
100
|
+
options: w.options,
|
|
101
|
+
default: w.default ?? null,
|
|
102
|
+
placeholder: w.placeholder ?? null
|
|
103
|
+
};
|
|
104
|
+
case "radio_input":
|
|
105
|
+
return {
|
|
106
|
+
...base,
|
|
107
|
+
type: "radio_input",
|
|
108
|
+
options: w.options,
|
|
109
|
+
default: w.default ?? null,
|
|
110
|
+
variant: w.variant ?? "native"
|
|
111
|
+
};
|
|
112
|
+
case "checkbox_input":
|
|
113
|
+
return {
|
|
114
|
+
...base,
|
|
115
|
+
type: "checkbox_input",
|
|
116
|
+
options: w.options,
|
|
117
|
+
default: w.default ?? null,
|
|
118
|
+
variant: w.variant ?? "native"
|
|
119
|
+
};
|
|
120
|
+
case "file_input":
|
|
121
|
+
return {
|
|
122
|
+
...base,
|
|
123
|
+
type: "file_input",
|
|
124
|
+
uploadUrl: w.upload_url,
|
|
125
|
+
accept: w.accept ?? null,
|
|
126
|
+
multiple: w.multiple ?? false
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function toFormEventData(data) {
|
|
131
|
+
const elements = data.elements ?? [];
|
|
132
|
+
return {
|
|
133
|
+
message: data.message ?? "",
|
|
134
|
+
elements: elements.map(toFormElement),
|
|
135
|
+
submitLabel: data.submit_label ?? "Send",
|
|
136
|
+
cancelLabel: data.cancel_label ?? "Cancel",
|
|
137
|
+
size: data.size ?? "medium"
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function toFormResponseEventData(data) {
|
|
141
|
+
return {
|
|
142
|
+
formEventId: data.form_event_id ?? "",
|
|
143
|
+
cancelled: data.cancelled ?? false,
|
|
144
|
+
values: data.values ?? {}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
71
147
|
function toThreadEvent(w) {
|
|
72
148
|
return {
|
|
73
149
|
id: w.id,
|
|
@@ -375,13 +451,7 @@ var WorkerClient = class {
|
|
|
375
451
|
threadId: params.threadId ?? "",
|
|
376
452
|
clientEventId
|
|
377
453
|
});
|
|
378
|
-
const promise =
|
|
379
|
-
const timer = setTimeout(() => {
|
|
380
|
-
this.pendingMessages.delete(clientEventId);
|
|
381
|
-
reject(new Error("sendMessage timed out waiting for server response"));
|
|
382
|
-
}, SEND_MESSAGE_TIMEOUT);
|
|
383
|
-
this.pendingMessages.set(clientEventId, { resolve, timer });
|
|
384
|
-
});
|
|
454
|
+
const promise = this.createPendingPromise(clientEventId);
|
|
385
455
|
this.send("event.create", {
|
|
386
456
|
thread_id: params.threadId,
|
|
387
457
|
client_event_id: clientEventId,
|
|
@@ -393,7 +463,35 @@ var WorkerClient = class {
|
|
|
393
463
|
});
|
|
394
464
|
return promise;
|
|
395
465
|
}
|
|
466
|
+
sendFormResponse(params) {
|
|
467
|
+
const clientEventId = crypto.randomUUID();
|
|
468
|
+
const promise = this.createPendingPromise(clientEventId);
|
|
469
|
+
this.send("event.create", {
|
|
470
|
+
thread_id: params.threadId,
|
|
471
|
+
client_event_id: clientEventId,
|
|
472
|
+
event: {
|
|
473
|
+
type: "form.response",
|
|
474
|
+
actor: "user",
|
|
475
|
+
content: [],
|
|
476
|
+
data: {
|
|
477
|
+
form_event_id: params.formEventId,
|
|
478
|
+
cancelled: params.cancelled,
|
|
479
|
+
values: params.values
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
return promise;
|
|
484
|
+
}
|
|
396
485
|
// --- Private ---
|
|
486
|
+
createPendingPromise(clientEventId) {
|
|
487
|
+
return new Promise((resolve, reject) => {
|
|
488
|
+
const timer = setTimeout(() => {
|
|
489
|
+
this.pendingMessages.delete(clientEventId);
|
|
490
|
+
reject(new Error("Request timed out waiting for server response"));
|
|
491
|
+
}, SEND_MESSAGE_TIMEOUT);
|
|
492
|
+
this.pendingMessages.set(clientEventId, { resolve, timer });
|
|
493
|
+
});
|
|
494
|
+
}
|
|
397
495
|
createConnection() {
|
|
398
496
|
if (this.ws) {
|
|
399
497
|
this.ws.onopen = null;
|
|
@@ -609,6 +707,23 @@ var WorkerClient = class {
|
|
|
609
707
|
});
|
|
610
708
|
}
|
|
611
709
|
};
|
|
710
|
+
function extractFormStates(events, existing) {
|
|
711
|
+
let result = existing;
|
|
712
|
+
for (const e of events) {
|
|
713
|
+
if (e.type === "form.response" && e.data) {
|
|
714
|
+
const formEventId = e.data.form_event_id;
|
|
715
|
+
if (formEventId && !result[formEventId]) {
|
|
716
|
+
const cancelled = e.data.cancelled ?? false;
|
|
717
|
+
const values = e.data.values ?? {};
|
|
718
|
+
result = {
|
|
719
|
+
...result,
|
|
720
|
+
[formEventId]: { values, submitting: false, submitted: !cancelled, cancelled }
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return result;
|
|
726
|
+
}
|
|
612
727
|
function createWorkerStore() {
|
|
613
728
|
return createStore()((set) => ({
|
|
614
729
|
connection: {
|
|
@@ -633,6 +748,7 @@ function createWorkerStore() {
|
|
|
633
748
|
isLoading: true
|
|
634
749
|
},
|
|
635
750
|
feedback: {},
|
|
751
|
+
formStates: {},
|
|
636
752
|
analytics: {
|
|
637
753
|
events: [],
|
|
638
754
|
counters: { totalRequests: 0, feedbackLikes: 0, feedbackDislikes: 0 }
|
|
@@ -697,6 +813,14 @@ function createWorkerStore() {
|
|
|
697
813
|
subscriptions.delete(threadId);
|
|
698
814
|
const loadingThreads = new Set(s.loadingThreads);
|
|
699
815
|
loadingThreads.delete(threadId);
|
|
816
|
+
const deletedEvents = s.events[threadId] ?? [];
|
|
817
|
+
const deletedEventIds = new Set(deletedEvents.map((e) => e.id));
|
|
818
|
+
const formStates = { ...s.formStates };
|
|
819
|
+
for (const key of Object.keys(formStates)) {
|
|
820
|
+
if (deletedEventIds.has(key)) {
|
|
821
|
+
delete formStates[key];
|
|
822
|
+
}
|
|
823
|
+
}
|
|
700
824
|
return {
|
|
701
825
|
threads,
|
|
702
826
|
threadOrder,
|
|
@@ -706,7 +830,8 @@ function createWorkerStore() {
|
|
|
706
830
|
optimisticKeys,
|
|
707
831
|
eventPagination,
|
|
708
832
|
subscriptions,
|
|
709
|
-
loadingThreads
|
|
833
|
+
loadingThreads,
|
|
834
|
+
formStates
|
|
710
835
|
};
|
|
711
836
|
}),
|
|
712
837
|
onThreadSnapshot: (thread, snapshotEvents, runs) => set((s) => {
|
|
@@ -734,7 +859,8 @@ function createWorkerStore() {
|
|
|
734
859
|
runs: { ...s.runs, [thread.id]: runs },
|
|
735
860
|
optimistic,
|
|
736
861
|
optimisticKeys,
|
|
737
|
-
loadingThreads
|
|
862
|
+
loadingThreads,
|
|
863
|
+
formStates: extractFormStates(mergedEvents, s.formStates)
|
|
738
864
|
};
|
|
739
865
|
}),
|
|
740
866
|
onEventAppend: (threadId, newEvents) => set((s) => {
|
|
@@ -751,7 +877,8 @@ function createWorkerStore() {
|
|
|
751
877
|
}
|
|
752
878
|
const merged = updates.size > 0 ? existing.map((e) => updates.get(e.id) ?? e) : existing;
|
|
753
879
|
return {
|
|
754
|
-
events: { ...s.events, [threadId]: [...merged, ...appends] }
|
|
880
|
+
events: { ...s.events, [threadId]: [...merged, ...appends] },
|
|
881
|
+
formStates: extractFormStates(appends, s.formStates)
|
|
755
882
|
};
|
|
756
883
|
}),
|
|
757
884
|
reconcileEvents: (threadId, newEvents, resolvedClientEventIds) => set((s) => {
|
|
@@ -873,7 +1000,64 @@ function createWorkerStore() {
|
|
|
873
1000
|
events: [],
|
|
874
1001
|
counters: { totalRequests: 0, feedbackLikes: 0, feedbackDislikes: 0 }
|
|
875
1002
|
}
|
|
876
|
-
}))
|
|
1003
|
+
})),
|
|
1004
|
+
initFormState: (formEventId, defaults) => set((s) => {
|
|
1005
|
+
if (s.formStates[formEventId]) return s;
|
|
1006
|
+
return {
|
|
1007
|
+
formStates: {
|
|
1008
|
+
...s.formStates,
|
|
1009
|
+
[formEventId]: {
|
|
1010
|
+
values: defaults,
|
|
1011
|
+
submitting: false,
|
|
1012
|
+
submitted: false,
|
|
1013
|
+
cancelled: false
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
}),
|
|
1018
|
+
setFormSubmitting: (formEventId, submitting) => set((s) => {
|
|
1019
|
+
const existing = s.formStates[formEventId];
|
|
1020
|
+
if (!existing) return s;
|
|
1021
|
+
return {
|
|
1022
|
+
formStates: {
|
|
1023
|
+
...s.formStates,
|
|
1024
|
+
[formEventId]: { ...existing, submitting }
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
}),
|
|
1028
|
+
setFormValue: (formEventId, fieldName, value) => set((s) => {
|
|
1029
|
+
const existing = s.formStates[formEventId];
|
|
1030
|
+
if (!existing || existing.submitted || existing.cancelled) return s;
|
|
1031
|
+
return {
|
|
1032
|
+
formStates: {
|
|
1033
|
+
...s.formStates,
|
|
1034
|
+
[formEventId]: {
|
|
1035
|
+
...existing,
|
|
1036
|
+
values: { ...existing.values, [fieldName]: value }
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
}),
|
|
1041
|
+
setFormSubmitted: (formEventId) => set((s) => {
|
|
1042
|
+
const existing = s.formStates[formEventId];
|
|
1043
|
+
if (!existing) return s;
|
|
1044
|
+
return {
|
|
1045
|
+
formStates: {
|
|
1046
|
+
...s.formStates,
|
|
1047
|
+
[formEventId]: { ...existing, submitting: false, submitted: true }
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
}),
|
|
1051
|
+
setFormCancelled: (formEventId) => set((s) => {
|
|
1052
|
+
const existing = s.formStates[formEventId];
|
|
1053
|
+
if (!existing) return s;
|
|
1054
|
+
return {
|
|
1055
|
+
formStates: {
|
|
1056
|
+
...s.formStates,
|
|
1057
|
+
[formEventId]: { ...existing, submitting: false, cancelled: true }
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
})
|
|
877
1061
|
}
|
|
878
1062
|
}));
|
|
879
1063
|
}
|
|
@@ -1093,6 +1277,148 @@ function useFeedback(targetId, targetType = "event") {
|
|
|
1093
1277
|
);
|
|
1094
1278
|
return { feedback, like, dislike };
|
|
1095
1279
|
}
|
|
1280
|
+
function useForm(formEventId, formData, threadId) {
|
|
1281
|
+
const { client, store } = useWorkerContext();
|
|
1282
|
+
const formState = useStore((s) => s.formStates[formEventId]);
|
|
1283
|
+
const [errors, setErrors] = useState({});
|
|
1284
|
+
useEffect(() => {
|
|
1285
|
+
const defaults = {};
|
|
1286
|
+
for (const el of formData.elements) {
|
|
1287
|
+
if ("default" in el && el.default != null) {
|
|
1288
|
+
defaults[el.name] = el.default;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
store.getState().actions.initFormState(formEventId, defaults);
|
|
1292
|
+
}, [formEventId, formData.elements, store]);
|
|
1293
|
+
const values = formState?.values ?? {};
|
|
1294
|
+
const isSubmitting = formState?.submitting ?? false;
|
|
1295
|
+
const isSubmitted = formState?.submitted ?? false;
|
|
1296
|
+
const isCancelled = formState?.cancelled ?? false;
|
|
1297
|
+
const isDisabled = isSubmitting || isSubmitted || isCancelled;
|
|
1298
|
+
const setValue = useCallback(
|
|
1299
|
+
(fieldName, value) => {
|
|
1300
|
+
store.getState().actions.setFormValue(formEventId, fieldName, value);
|
|
1301
|
+
setErrors((prev) => {
|
|
1302
|
+
if (!prev[fieldName]) return prev;
|
|
1303
|
+
const next = { ...prev };
|
|
1304
|
+
delete next[fieldName];
|
|
1305
|
+
return next;
|
|
1306
|
+
});
|
|
1307
|
+
},
|
|
1308
|
+
[formEventId, store]
|
|
1309
|
+
);
|
|
1310
|
+
const validate = useCallback(() => {
|
|
1311
|
+
const currentValues = store.getState().formStates[formEventId]?.values ?? {};
|
|
1312
|
+
const newErrors = {};
|
|
1313
|
+
for (const el of formData.elements) {
|
|
1314
|
+
if (!el.required) continue;
|
|
1315
|
+
const val = currentValues[el.name];
|
|
1316
|
+
if (isEmptyValue(el, val)) {
|
|
1317
|
+
newErrors[el.name] = "Required";
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
setErrors(newErrors);
|
|
1321
|
+
return Object.keys(newErrors).length === 0;
|
|
1322
|
+
}, [formEventId, formData.elements, store]);
|
|
1323
|
+
const submit = useCallback(async () => {
|
|
1324
|
+
const state = store.getState();
|
|
1325
|
+
const fs = state.formStates[formEventId];
|
|
1326
|
+
if (!fs || fs.submitting || fs.submitted || fs.cancelled) return;
|
|
1327
|
+
if (!validate()) return;
|
|
1328
|
+
state.actions.setFormSubmitting(formEventId, true);
|
|
1329
|
+
const currentValues = state.formStates[formEventId]?.values ?? {};
|
|
1330
|
+
if (!threadId) {
|
|
1331
|
+
state.actions.setFormSubmitted(formEventId);
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
try {
|
|
1335
|
+
await client.sendFormResponse({
|
|
1336
|
+
threadId,
|
|
1337
|
+
formEventId,
|
|
1338
|
+
cancelled: false,
|
|
1339
|
+
values: currentValues
|
|
1340
|
+
});
|
|
1341
|
+
store.getState().actions.setFormSubmitted(formEventId);
|
|
1342
|
+
} catch {
|
|
1343
|
+
store.getState().actions.setFormSubmitting(formEventId, false);
|
|
1344
|
+
}
|
|
1345
|
+
}, [formEventId, threadId, validate, client, store]);
|
|
1346
|
+
const cancel = useCallback(async () => {
|
|
1347
|
+
const state = store.getState();
|
|
1348
|
+
const fs = state.formStates[formEventId];
|
|
1349
|
+
if (!fs || fs.submitting || fs.submitted || fs.cancelled) return;
|
|
1350
|
+
state.actions.setFormSubmitting(formEventId, true);
|
|
1351
|
+
if (!threadId) {
|
|
1352
|
+
state.actions.setFormCancelled(formEventId);
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
try {
|
|
1356
|
+
await client.sendFormResponse({
|
|
1357
|
+
threadId,
|
|
1358
|
+
formEventId,
|
|
1359
|
+
cancelled: true,
|
|
1360
|
+
values: {}
|
|
1361
|
+
});
|
|
1362
|
+
store.getState().actions.setFormCancelled(formEventId);
|
|
1363
|
+
} catch {
|
|
1364
|
+
store.getState().actions.setFormSubmitting(formEventId, false);
|
|
1365
|
+
}
|
|
1366
|
+
}, [formEventId, threadId, client, store]);
|
|
1367
|
+
return {
|
|
1368
|
+
values,
|
|
1369
|
+
errors,
|
|
1370
|
+
setValue,
|
|
1371
|
+
validate,
|
|
1372
|
+
submit,
|
|
1373
|
+
cancel,
|
|
1374
|
+
isSubmitting,
|
|
1375
|
+
isSubmitted,
|
|
1376
|
+
isCancelled,
|
|
1377
|
+
isDisabled
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
function isEmptyValue(element, val) {
|
|
1381
|
+
if (val == null) return true;
|
|
1382
|
+
if (typeof val === "string" && val.trim() === "") return true;
|
|
1383
|
+
if (element.type === "checkbox_input" && Array.isArray(val) && val.length === 0) return true;
|
|
1384
|
+
return false;
|
|
1385
|
+
}
|
|
1386
|
+
function useFormFileUpload() {
|
|
1387
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
1388
|
+
const [error, setError] = useState(null);
|
|
1389
|
+
const upload = useCallback(
|
|
1390
|
+
async (params) => {
|
|
1391
|
+
setIsUploading(true);
|
|
1392
|
+
setError(null);
|
|
1393
|
+
try {
|
|
1394
|
+
const formData = new FormData();
|
|
1395
|
+
formData.append("file", params.file);
|
|
1396
|
+
formData.append("field_id", params.fieldId);
|
|
1397
|
+
formData.append("agent_id", params.agentId);
|
|
1398
|
+
formData.append("run_id", params.runId);
|
|
1399
|
+
formData.append("thread_id", params.threadId);
|
|
1400
|
+
const response = await fetch(params.uploadUrl, {
|
|
1401
|
+
method: "POST",
|
|
1402
|
+
body: formData
|
|
1403
|
+
});
|
|
1404
|
+
if (!response.ok) {
|
|
1405
|
+
const detail = await response.text().catch(() => "");
|
|
1406
|
+
throw new Error(detail || `Upload failed (${response.status})`);
|
|
1407
|
+
}
|
|
1408
|
+
return await response.json();
|
|
1409
|
+
} catch (err) {
|
|
1410
|
+
const message = err instanceof Error ? err.message : "Upload failed";
|
|
1411
|
+
setError(message);
|
|
1412
|
+
throw err;
|
|
1413
|
+
} finally {
|
|
1414
|
+
setIsUploading(false);
|
|
1415
|
+
}
|
|
1416
|
+
},
|
|
1417
|
+
[]
|
|
1418
|
+
);
|
|
1419
|
+
const clearError = useCallback(() => setError(null), []);
|
|
1420
|
+
return { upload, isUploading, error, clearError };
|
|
1421
|
+
}
|
|
1096
1422
|
function useMessage() {
|
|
1097
1423
|
const { client } = useWorkerContext();
|
|
1098
1424
|
const send = useCallback(
|
|
@@ -1199,6 +1525,6 @@ function useUpload() {
|
|
|
1199
1525
|
return { upload, isUploading };
|
|
1200
1526
|
}
|
|
1201
1527
|
|
|
1202
|
-
export { WorkerProvider, isSettingsFieldGroup, useAnalytics, useAudioRecorder, useConnection, useFeedback, useMessage, useSettings, useThreadDetails, useThreadEvents, useThreadsActions, useThreadsList, useUpload };
|
|
1528
|
+
export { WorkerProvider, isSettingsFieldGroup, toFormElement, toFormEventData, toFormResponseEventData, useAnalytics, useAudioRecorder, useConnection, useFeedback, useForm, useFormFileUpload, useMessage, useSettings, useThreadDetails, useThreadEvents, useThreadsActions, useThreadsList, useUpload };
|
|
1203
1529
|
//# sourceMappingURL=main.js.map
|
|
1204
1530
|
//# sourceMappingURL=main.js.map
|