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.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 = new Promise((resolve, reject) => {
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