@streamoid/chat-components 0.2.6 → 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 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) {
@@ -811,6 +852,88 @@ ${valueSummaryParts.join("\n")}` : "";
811
852
  }
812
853
  )
813
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
+ }
814
937
  case "todo_list": {
815
938
  const todoItems = formValues[field.id] || [];
816
939
  const completedCount = todoItems.filter((i) => i.status === "completed").length;
@@ -1113,7 +1236,7 @@ var dynamicFormManifest = {
1113
1236
  name: "common:dynamic-form",
1114
1237
  aliases: ["dynamic_form"],
1115
1238
  service: "common",
1116
- 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",
1117
1240
  containerStyle: {
1118
1241
  display: "flex",
1119
1242
  position: "relative",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamoid/chat-components",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Shared chat UI components for the Streamoid chat host — DynamicForm and other cross-service components",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",