@streamoid/chat-components 0.2.7 → 0.2.9

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.
Files changed (2) hide show
  1. package/dist/index.js +115 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -417,6 +417,38 @@ 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 getTextValue = (value, fallback = "") => {
421
+ if (typeof value === "string") return value;
422
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
423
+ if (value && typeof value === "object") {
424
+ const obj = value;
425
+ if (typeof obj.label === "string") return obj.label;
426
+ if (typeof obj.text === "string") return obj.text;
427
+ if (typeof obj.value === "string") return obj.value;
428
+ }
429
+ return fallback;
430
+ };
431
+ const normalizeMediaUrls = (raw) => {
432
+ if (Array.isArray(raw)) {
433
+ return raw.filter((item) => typeof item === "string" && item.trim().length > 0);
434
+ }
435
+ if (typeof raw === "string" && raw.trim().length > 0) {
436
+ const maybeJson = raw.trim();
437
+ if (maybeJson.startsWith("[") && maybeJson.endsWith("]")) {
438
+ try {
439
+ const parsed = JSON.parse(maybeJson);
440
+ if (Array.isArray(parsed)) {
441
+ return parsed.filter(
442
+ (item) => typeof item === "string" && item.trim().length > 0
443
+ );
444
+ }
445
+ } catch {
446
+ }
447
+ }
448
+ return maybeJson.split(/[\n,]/).map((part) => part.trim()).filter(Boolean);
449
+ }
450
+ return [];
451
+ };
420
452
  const inferMediaTypeFromUrl = (url) => {
421
453
  const cleanUrl = (url || "").split("?")[0].toLowerCase();
422
454
  const videoExts = [".mp4", ".mov", ".webm", ".m4v", ".avi", ".mkv"];
@@ -438,7 +470,7 @@ function DynamicForm(props) {
438
470
  } else if (field.type === "approval") {
439
471
  values[field.id] = null;
440
472
  } else if (field.type === "media_approval") {
441
- const mediaUrls = Array.isArray(field.media_urls) ? field.media_urls.filter(Boolean) : [];
473
+ const mediaUrls = normalizeMediaUrls(field.media_urls);
442
474
  values[field.id] = {
443
475
  decision: null,
444
476
  feedback: "",
@@ -462,6 +494,7 @@ function DynamicForm(props) {
462
494
  const [editingTodoContent, setEditingTodoContent] = useState("");
463
495
  const [newTodoInputs, setNewTodoInputs] = useState({});
464
496
  const [fileUploadState, setFileUploadState] = useState({});
497
+ const [mediaPreview, setMediaPreview] = useState(null);
465
498
  const scrollRef = useRef(null);
466
499
  const [canScrollUp, setCanScrollUp] = useState(false);
467
500
  const [canScrollDown, setCanScrollDown] = useState(false);
@@ -694,7 +727,13 @@ function DynamicForm(props) {
694
727
  const field = fields.find((f) => f.id === fieldId);
695
728
  const fieldLabel = field?.label || fieldId;
696
729
  let displayVal;
697
- if (field?.options && typeof val === "string") {
730
+ if (field?.type === "media_approval" && val && typeof val === "object") {
731
+ const mediaVal = val;
732
+ const decisionText = mediaVal.decision ?? "pending";
733
+ const feedbackText = mediaVal.feedback?.trim() || "none";
734
+ const mediaCount = Array.isArray(mediaVal.media_urls) ? mediaVal.media_urls.length : 0;
735
+ displayVal = `decision=${decisionText}, feedback=${feedbackText}, media_count=${mediaCount}`;
736
+ } else if (field?.options && typeof val === "string") {
698
737
  const opt = field.options.find((o) => o.id === val);
699
738
  displayVal = opt?.label || val;
700
739
  } else if (field?.options && Array.isArray(val)) {
@@ -853,13 +892,13 @@ ${valueSummaryParts.join("\n")}` : "";
853
892
  )
854
893
  ] });
855
894
  case "media_approval": {
856
- const mediaUrls = Array.isArray(field.media_urls) ? field.media_urls.filter(Boolean) : [];
857
895
  const mediaValue = formValues[field.id] ?? {
858
896
  decision: null,
859
897
  feedback: "",
860
- media_urls: mediaUrls,
898
+ media_urls: normalizeMediaUrls(field.media_urls),
861
899
  media_type: field.media_type ?? "mixed"
862
900
  };
901
+ const mediaUrls = normalizeMediaUrls(field.media_urls ?? mediaValue.media_urls);
863
902
  const feedbackLabel = field.feedback_label ?? "What should be changed?";
864
903
  const feedbackPlaceholder = field.feedback_placeholder ?? "Describe what needs to be modified.";
865
904
  const approveLabel = field.approve_label ?? "Approve";
@@ -871,13 +910,29 @@ ${valueSummaryParts.join("\n")}` : "";
871
910
  "div",
872
911
  {
873
912
  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",
913
+ children: /* @__PURE__ */ jsx10(
914
+ "button",
876
915
  {
877
- src: url,
878
- alt: `Generated media ${idx + 1}`,
879
- className: "w-full h-44 object-cover",
880
- loading: "lazy"
916
+ type: "button",
917
+ onClick: () => setMediaPreview({ url, type: mediaType }),
918
+ className: "w-full text-left cursor-zoom-in",
919
+ title: "Click to view full media",
920
+ children: mediaType === "video" ? /* @__PURE__ */ jsx10(
921
+ "video",
922
+ {
923
+ src: url,
924
+ controls: true,
925
+ className: "w-full h-44 object-cover bg-black"
926
+ }
927
+ ) : /* @__PURE__ */ jsx10(
928
+ "img",
929
+ {
930
+ src: url,
931
+ alt: `Generated media ${idx + 1}`,
932
+ className: "w-full h-44 object-cover",
933
+ loading: "lazy"
934
+ }
935
+ )
881
936
  }
882
937
  )
883
938
  },
@@ -893,7 +948,7 @@ ${valueSummaryParts.join("\n")}` : "";
893
948
  onChange: (e) => handleFieldChange(field.id, {
894
949
  ...mediaValue,
895
950
  feedback: e.target.value,
896
- media_urls: mediaValue.media_urls ?? mediaUrls,
951
+ media_urls: normalizeMediaUrls(mediaValue.media_urls ?? mediaUrls),
897
952
  media_type: mediaValue.media_type ?? field.media_type ?? "mixed"
898
953
  }),
899
954
  placeholder: feedbackPlaceholder,
@@ -1181,12 +1236,14 @@ ${valueSummaryParts.join("\n")}` : "";
1181
1236
  children: /* @__PURE__ */ jsxs2("div", { className: "space-y-3 pb-2", children: [
1182
1237
  fields.map((field) => {
1183
1238
  const statusText = getFieldStatusText(field);
1239
+ const labelText = getTextValue(field.label, field.id);
1240
+ const descriptionText = getTextValue(field.description, "");
1184
1241
  return /* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
1185
1242
  /* @__PURE__ */ jsxs2(Label2, { className: "text-xs font-medium flex items-center gap-1.5", children: [
1186
- /* @__PURE__ */ jsx10("span", { children: field.label }),
1243
+ /* @__PURE__ */ jsx10("span", { children: labelText }),
1187
1244
  field.required && /* @__PURE__ */ jsx10("span", { className: "text-destructive text-[10px]", children: "*" })
1188
1245
  ] }),
1189
- field.description && /* @__PURE__ */ jsx10("p", { className: "text-[11px] text-muted-foreground leading-snug", children: field.description }),
1246
+ descriptionText && /* @__PURE__ */ jsx10("p", { className: "text-[11px] text-muted-foreground leading-snug", children: descriptionText }),
1190
1247
  renderField(field),
1191
1248
  statusText && field.type !== "file" && field.type !== "todo_list" && /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1 text-emerald-600 mt-0.5", children: [
1192
1249
  /* @__PURE__ */ jsx10(CheckCircle, { className: "w-2.5 h-2.5" }),
@@ -1202,7 +1259,7 @@ ${valueSummaryParts.join("\n")}` : "";
1202
1259
  }
1203
1260
  ),
1204
1261
  /* @__PURE__ */ jsx10("div", { className: `scroll-fade-bottom ${!canScrollDown ? "fade-hidden" : ""}` }),
1205
- /* @__PURE__ */ jsxs2("div", { className: "flex justify-end gap-2 px-5 py-2 flex-shrink-0", children: [
1262
+ !(fields.length === 1 && fields[0]?.type === "media_approval") && /* @__PURE__ */ jsxs2("div", { className: "flex justify-end gap-2 px-5 py-2 flex-shrink-0", children: [
1206
1263
  cancelText && /* @__PURE__ */ jsx10(
1207
1264
  Button,
1208
1265
  {
@@ -1227,7 +1284,50 @@ ${valueSummaryParts.join("\n")}` : "";
1227
1284
  }
1228
1285
  )
1229
1286
  ] })
1230
- ] })
1287
+ ] }),
1288
+ mediaPreview && /* @__PURE__ */ jsx10(
1289
+ "div",
1290
+ {
1291
+ className: "fixed inset-0 z-50 bg-black/80 flex items-center justify-center p-4",
1292
+ onClick: () => setMediaPreview(null),
1293
+ children: /* @__PURE__ */ jsxs2(
1294
+ "div",
1295
+ {
1296
+ className: "relative w-full max-w-5xl max-h-[90vh] bg-background rounded-lg border border-border overflow-hidden",
1297
+ onClick: (e) => e.stopPropagation(),
1298
+ children: [
1299
+ /* @__PURE__ */ jsx10(
1300
+ Button,
1301
+ {
1302
+ type: "button",
1303
+ variant: "ghost",
1304
+ size: "sm",
1305
+ onClick: () => setMediaPreview(null),
1306
+ className: "absolute right-2 top-2 z-10 bg-background/80 hover:bg-background",
1307
+ children: /* @__PURE__ */ jsx10(X, { className: "w-4 h-4" })
1308
+ }
1309
+ ),
1310
+ /* @__PURE__ */ jsx10("div", { className: "w-full h-full p-2 flex items-center justify-center", children: mediaPreview.type === "video" ? /* @__PURE__ */ jsx10(
1311
+ "video",
1312
+ {
1313
+ src: mediaPreview.url,
1314
+ controls: true,
1315
+ autoPlay: true,
1316
+ className: "max-w-full max-h-[84vh] object-contain bg-black rounded"
1317
+ }
1318
+ ) : /* @__PURE__ */ jsx10(
1319
+ "img",
1320
+ {
1321
+ src: mediaPreview.url,
1322
+ alt: "Full preview",
1323
+ className: "max-w-full max-h-[84vh] object-contain rounded"
1324
+ }
1325
+ ) })
1326
+ ]
1327
+ }
1328
+ )
1329
+ }
1330
+ )
1231
1331
  ] });
1232
1332
  }
1233
1333
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamoid/chat-components",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
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",