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