@streamoid/chat-components 0.2.5 → 0.2.7
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/index.d.ts +9 -2
- package/dist/index.js +143 -2
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -10,9 +10,10 @@ interface TodoItem {
|
|
|
10
10
|
content: string;
|
|
11
11
|
status: "pending" | "completed";
|
|
12
12
|
}
|
|
13
|
+
type MediaType = "image" | "video" | "mixed";
|
|
13
14
|
interface DynamicFormField {
|
|
14
15
|
id: string;
|
|
15
|
-
type: "text" | "textarea" | "number" | "select" | "multiselect" | "checkbox" | "radio" | "approval" | "todo_list" | "file";
|
|
16
|
+
type: "text" | "textarea" | "number" | "select" | "multiselect" | "checkbox" | "radio" | "approval" | "media_approval" | "todo_list" | "file";
|
|
16
17
|
label?: string;
|
|
17
18
|
description?: string;
|
|
18
19
|
placeholder?: string;
|
|
@@ -22,6 +23,12 @@ interface DynamicFormField {
|
|
|
22
23
|
items?: TodoItem[];
|
|
23
24
|
accept?: string;
|
|
24
25
|
multiple?: boolean;
|
|
26
|
+
media_urls?: string[];
|
|
27
|
+
media_type?: MediaType;
|
|
28
|
+
approve_label?: string;
|
|
29
|
+
reject_label?: string;
|
|
30
|
+
feedback_label?: string;
|
|
31
|
+
feedback_placeholder?: string;
|
|
25
32
|
}
|
|
26
33
|
interface DynamicFormProps {
|
|
27
34
|
workspaceId: string;
|
|
@@ -55,7 +62,7 @@ declare const dynamicFormManifest: {
|
|
|
55
62
|
readonly name: "common:dynamic-form";
|
|
56
63
|
readonly aliases: readonly ["dynamic_form"];
|
|
57
64
|
readonly service: "common";
|
|
58
|
-
readonly description: "Server-driven dynamic form supporting text, textarea, number, select, multiselect, checkbox, radio, approval, todo_list, and file upload fields";
|
|
65
|
+
readonly description: "Server-driven dynamic form supporting text, textarea, number, select, multiselect, checkbox, radio, approval, media_approval, todo_list, and file upload fields";
|
|
59
66
|
readonly containerStyle: {
|
|
60
67
|
readonly display: "flex";
|
|
61
68
|
readonly position: "relative";
|
package/dist/index.js
CHANGED
|
@@ -417,6 +417,14 @@ function DynamicForm(props) {
|
|
|
417
417
|
const fields = uiProps.fields ?? [];
|
|
418
418
|
const submitText = uiProps.submit_button_text ?? "Submit";
|
|
419
419
|
const cancelText = uiProps.cancel_button_text;
|
|
420
|
+
const inferMediaTypeFromUrl = (url) => {
|
|
421
|
+
const cleanUrl = (url || "").split("?")[0].toLowerCase();
|
|
422
|
+
const videoExts = [".mp4", ".mov", ".webm", ".m4v", ".avi", ".mkv"];
|
|
423
|
+
const imageExts = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".svg", ".heic"];
|
|
424
|
+
if (videoExts.some((ext) => cleanUrl.endsWith(ext))) return "video";
|
|
425
|
+
if (imageExts.some((ext) => cleanUrl.endsWith(ext))) return "image";
|
|
426
|
+
return "image";
|
|
427
|
+
};
|
|
420
428
|
const getInitialValues = () => {
|
|
421
429
|
if (submittedValues) return { ...submittedValues };
|
|
422
430
|
const values = {};
|
|
@@ -429,6 +437,14 @@ function DynamicForm(props) {
|
|
|
429
437
|
values[field.id] = field.items.map((item) => ({ ...item }));
|
|
430
438
|
} else if (field.type === "approval") {
|
|
431
439
|
values[field.id] = null;
|
|
440
|
+
} else if (field.type === "media_approval") {
|
|
441
|
+
const mediaUrls = Array.isArray(field.media_urls) ? field.media_urls.filter(Boolean) : [];
|
|
442
|
+
values[field.id] = {
|
|
443
|
+
decision: null,
|
|
444
|
+
feedback: "",
|
|
445
|
+
media_urls: mediaUrls,
|
|
446
|
+
media_type: field.media_type ?? "mixed"
|
|
447
|
+
};
|
|
432
448
|
} else {
|
|
433
449
|
values[field.id] = "";
|
|
434
450
|
}
|
|
@@ -629,6 +645,31 @@ function DynamicForm(props) {
|
|
|
629
645
|
handleFieldChange(fieldId, action);
|
|
630
646
|
await submitForm(approved ? "Approve" : "Reject", { ...formValues, [fieldId]: action });
|
|
631
647
|
};
|
|
648
|
+
const handleMediaApprovalDecision = async (field, decision) => {
|
|
649
|
+
const current = formValues[field.id] ?? {
|
|
650
|
+
decision: null,
|
|
651
|
+
feedback: "",
|
|
652
|
+
media_urls: Array.isArray(field.media_urls) ? field.media_urls : [],
|
|
653
|
+
media_type: field.media_type ?? "mixed"
|
|
654
|
+
};
|
|
655
|
+
const feedback = current.feedback?.trim() ?? "";
|
|
656
|
+
if (decision === "needs_modification" && !feedback) {
|
|
657
|
+
setError("Feedback is required when requesting modifications.");
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const nextValue = {
|
|
661
|
+
...current,
|
|
662
|
+
decision,
|
|
663
|
+
feedback,
|
|
664
|
+
media_urls: current.media_urls ?? (Array.isArray(field.media_urls) ? field.media_urls : []),
|
|
665
|
+
media_type: current.media_type ?? field.media_type ?? "mixed"
|
|
666
|
+
};
|
|
667
|
+
handleFieldChange(field.id, nextValue);
|
|
668
|
+
await submitForm(
|
|
669
|
+
decision === "approved" ? "Approve" : "Needs Modification",
|
|
670
|
+
{ ...formValues, [field.id]: nextValue }
|
|
671
|
+
);
|
|
672
|
+
};
|
|
632
673
|
const validateForm = () => {
|
|
633
674
|
for (const field of fields) {
|
|
634
675
|
if (field.required) {
|
|
@@ -648,7 +689,25 @@ function DynamicForm(props) {
|
|
|
648
689
|
form_title: title,
|
|
649
690
|
values
|
|
650
691
|
};
|
|
651
|
-
const
|
|
692
|
+
const valueSummaryParts = [];
|
|
693
|
+
for (const [fieldId, val] of Object.entries(values)) {
|
|
694
|
+
const field = fields.find((f) => f.id === fieldId);
|
|
695
|
+
const fieldLabel = field?.label || fieldId;
|
|
696
|
+
let displayVal;
|
|
697
|
+
if (field?.options && typeof val === "string") {
|
|
698
|
+
const opt = field.options.find((o) => o.id === val);
|
|
699
|
+
displayVal = opt?.label || val;
|
|
700
|
+
} else if (field?.options && Array.isArray(val)) {
|
|
701
|
+
displayVal = val.map((v) => field.options.find((o) => o.id === v)?.label || v).join(", ");
|
|
702
|
+
} else {
|
|
703
|
+
displayVal = String(val ?? "");
|
|
704
|
+
}
|
|
705
|
+
valueSummaryParts.push(`${fieldLabel}: ${displayVal}`);
|
|
706
|
+
}
|
|
707
|
+
const valueSummary = valueSummaryParts.length > 0 ? `
|
|
708
|
+
Submitted values:
|
|
709
|
+
${valueSummaryParts.join("\n")}` : "";
|
|
710
|
+
const messageText = `User clicked "${userAction}" on form "${title}"${valueSummary}`;
|
|
652
711
|
try {
|
|
653
712
|
await onSubmit(resumePayload, messageText);
|
|
654
713
|
setSubmittedData({ user_action: userAction, values });
|
|
@@ -793,6 +852,88 @@ function DynamicForm(props) {
|
|
|
793
852
|
}
|
|
794
853
|
)
|
|
795
854
|
] });
|
|
855
|
+
case "media_approval": {
|
|
856
|
+
const mediaUrls = Array.isArray(field.media_urls) ? field.media_urls.filter(Boolean) : [];
|
|
857
|
+
const mediaValue = formValues[field.id] ?? {
|
|
858
|
+
decision: null,
|
|
859
|
+
feedback: "",
|
|
860
|
+
media_urls: mediaUrls,
|
|
861
|
+
media_type: field.media_type ?? "mixed"
|
|
862
|
+
};
|
|
863
|
+
const feedbackLabel = field.feedback_label ?? "What should be changed?";
|
|
864
|
+
const feedbackPlaceholder = field.feedback_placeholder ?? "Describe what needs to be modified.";
|
|
865
|
+
const approveLabel = field.approve_label ?? "Approve";
|
|
866
|
+
const rejectLabel = field.reject_label ?? "Needs Modification";
|
|
867
|
+
return /* @__PURE__ */ jsxs2("div", { className: "space-y-3", children: [
|
|
868
|
+
mediaUrls.length > 0 && /* @__PURE__ */ jsx10("div", { className: "grid gap-2 sm:grid-cols-2", children: mediaUrls.map((url, idx) => {
|
|
869
|
+
const mediaType = field.media_type && field.media_type !== "mixed" ? field.media_type : inferMediaTypeFromUrl(url);
|
|
870
|
+
return /* @__PURE__ */ jsx10(
|
|
871
|
+
"div",
|
|
872
|
+
{
|
|
873
|
+
className: "rounded-md border border-border overflow-hidden bg-muted/20",
|
|
874
|
+
children: mediaType === "video" ? /* @__PURE__ */ jsx10("video", { src: url, controls: true, className: "w-full h-44 object-cover bg-black" }) : /* @__PURE__ */ jsx10(
|
|
875
|
+
"img",
|
|
876
|
+
{
|
|
877
|
+
src: url,
|
|
878
|
+
alt: `Generated media ${idx + 1}`,
|
|
879
|
+
className: "w-full h-44 object-cover",
|
|
880
|
+
loading: "lazy"
|
|
881
|
+
}
|
|
882
|
+
)
|
|
883
|
+
},
|
|
884
|
+
`${field.id}-${idx}`
|
|
885
|
+
);
|
|
886
|
+
}) }),
|
|
887
|
+
/* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
|
|
888
|
+
/* @__PURE__ */ jsx10(Label2, { className: "text-xs font-medium", children: feedbackLabel }),
|
|
889
|
+
/* @__PURE__ */ jsx10(
|
|
890
|
+
Textarea,
|
|
891
|
+
{
|
|
892
|
+
value: mediaValue.feedback || "",
|
|
893
|
+
onChange: (e) => handleFieldChange(field.id, {
|
|
894
|
+
...mediaValue,
|
|
895
|
+
feedback: e.target.value,
|
|
896
|
+
media_urls: mediaValue.media_urls ?? mediaUrls,
|
|
897
|
+
media_type: mediaValue.media_type ?? field.media_type ?? "mixed"
|
|
898
|
+
}),
|
|
899
|
+
placeholder: feedbackPlaceholder,
|
|
900
|
+
className: "bg-background min-h-[72px] w-full text-xs"
|
|
901
|
+
}
|
|
902
|
+
)
|
|
903
|
+
] }),
|
|
904
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex gap-2 justify-end", children: [
|
|
905
|
+
/* @__PURE__ */ jsxs2(
|
|
906
|
+
Button,
|
|
907
|
+
{
|
|
908
|
+
type: "button",
|
|
909
|
+
variant: "destructive",
|
|
910
|
+
size: "sm",
|
|
911
|
+
onClick: () => handleMediaApprovalDecision(field, "needs_modification"),
|
|
912
|
+
className: "bg-destructive/10 text-destructive hover:bg-destructive/20 border-transparent shadow-none h-8 text-xs",
|
|
913
|
+
disabled: isLoading,
|
|
914
|
+
children: [
|
|
915
|
+
/* @__PURE__ */ jsx10(AlertCircle, { className: "w-3 h-3 mr-1.5" }),
|
|
916
|
+
rejectLabel
|
|
917
|
+
]
|
|
918
|
+
}
|
|
919
|
+
),
|
|
920
|
+
/* @__PURE__ */ jsxs2(
|
|
921
|
+
Button,
|
|
922
|
+
{
|
|
923
|
+
type: "button",
|
|
924
|
+
size: "sm",
|
|
925
|
+
onClick: () => handleMediaApprovalDecision(field, "approved"),
|
|
926
|
+
className: "bg-emerald-600 hover:bg-emerald-700 text-white shadow-none border-transparent h-8 text-xs",
|
|
927
|
+
disabled: isLoading,
|
|
928
|
+
children: [
|
|
929
|
+
/* @__PURE__ */ jsx10(CheckCircle, { className: "w-3 h-3 mr-1.5" }),
|
|
930
|
+
approveLabel
|
|
931
|
+
]
|
|
932
|
+
}
|
|
933
|
+
)
|
|
934
|
+
] })
|
|
935
|
+
] });
|
|
936
|
+
}
|
|
796
937
|
case "todo_list": {
|
|
797
938
|
const todoItems = formValues[field.id] || [];
|
|
798
939
|
const completedCount = todoItems.filter((i) => i.status === "completed").length;
|
|
@@ -1095,7 +1236,7 @@ var dynamicFormManifest = {
|
|
|
1095
1236
|
name: "common:dynamic-form",
|
|
1096
1237
|
aliases: ["dynamic_form"],
|
|
1097
1238
|
service: "common",
|
|
1098
|
-
description: "Server-driven dynamic form supporting text, textarea, number, select, multiselect, checkbox, radio, approval, todo_list, and file upload fields",
|
|
1239
|
+
description: "Server-driven dynamic form supporting text, textarea, number, select, multiselect, checkbox, radio, approval, media_approval, todo_list, and file upload fields",
|
|
1099
1240
|
containerStyle: {
|
|
1100
1241
|
display: "flex",
|
|
1101
1242
|
position: "relative",
|
package/package.json
CHANGED