@david-xpn/llm-ui-feedback 0.1.5 → 0.1.8

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.js CHANGED
@@ -66,31 +66,59 @@ function FloatingButton({ onPickClick, onPanelToggle, draftCount, panelOpen, pos
66
66
  gap: 8
67
67
  };
68
68
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: anchor, children: [
69
- draftCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
69
+ draftCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
70
70
  "button",
71
71
  {
72
72
  onClick: onPanelToggle,
73
73
  "aria-label": panelOpen ? "Close drafts panel" : `Open drafts panel (${draftCount})`,
74
74
  style: {
75
- width: 32,
76
- height: 32,
75
+ width: 36,
76
+ height: 36,
77
77
  borderRadius: "50%",
78
78
  border: "none",
79
79
  background: panelOpen ? "#ef4444" : buttonColor,
80
80
  color: panelOpen ? "#fff" : getContrastColor(buttonColor),
81
- fontFamily: "system-ui, -apple-system, sans-serif",
82
- fontSize: 13,
83
- fontWeight: 700,
84
- lineHeight: 1,
85
81
  cursor: "pointer",
86
82
  display: "flex",
87
83
  alignItems: "center",
88
84
  justifyContent: "center",
89
85
  boxShadow: "0 2px 8px rgba(0,0,0,0.25)",
90
86
  transition: "background 0.15s",
91
- padding: 0
87
+ padding: 0,
88
+ position: "relative"
92
89
  },
93
- children: draftCount
90
+ children: [
91
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
92
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "3", width: "2", height: "2", rx: "0.5", fill: "currentColor" }),
93
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "6", y: "3", width: "10", height: "2", rx: "0.5", fill: "currentColor" }),
94
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "8", width: "2", height: "2", rx: "0.5", fill: "currentColor" }),
95
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "6", y: "8", width: "10", height: "2", rx: "0.5", fill: "currentColor" }),
96
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "13", width: "2", height: "2", rx: "0.5", fill: "currentColor" }),
97
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "6", y: "13", width: "10", height: "2", rx: "0.5", fill: "currentColor" })
98
+ ] }),
99
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
100
+ "span",
101
+ {
102
+ style: {
103
+ position: "absolute",
104
+ top: -4,
105
+ right: -4,
106
+ minWidth: 16,
107
+ height: 16,
108
+ borderRadius: 8,
109
+ background: "#ef4444",
110
+ color: "#fff",
111
+ fontSize: 10,
112
+ fontWeight: 700,
113
+ fontFamily: "system-ui, -apple-system, sans-serif",
114
+ lineHeight: "16px",
115
+ textAlign: "center",
116
+ padding: "0 3px"
117
+ },
118
+ children: draftCount
119
+ }
120
+ )
121
+ ]
94
122
  }
95
123
  ),
96
124
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -668,6 +696,20 @@ function SidePanel({
668
696
 
669
697
  // src/components/DraftModal.tsx
670
698
  var import_react4 = require("react");
699
+
700
+ // src/utils/blob.ts
701
+ function dataUrlToBlob(dataUrl) {
702
+ const [header, base64] = dataUrl.split(",");
703
+ const mime = header.match(/:(.*?);/)?.[1] || "image/png";
704
+ const binary = atob(base64);
705
+ const bytes = new Uint8Array(binary.length);
706
+ for (let i = 0; i < binary.length; i++) {
707
+ bytes[i] = binary.charCodeAt(i);
708
+ }
709
+ return new Blob([bytes], { type: mime });
710
+ }
711
+
712
+ // src/components/DraftModal.tsx
671
713
  var import_jsx_runtime5 = require("react/jsx-runtime");
672
714
  var WIDGET_CONTAINER_ID2 = "llm-ui-feedback-root";
673
715
  function DraftModal({ pendingContext, addingDraft, onAdd, onCancel }) {
@@ -694,6 +736,46 @@ function DraftModal({ pendingContext, addingDraft, onAdd, onCancel }) {
694
736
  }
695
737
  };
696
738
  const { context, screenshot } = pendingContext;
739
+ const [copiedText, setCopiedText] = (0, import_react4.useState)(false);
740
+ const [copiedImage, setCopiedImage] = (0, import_react4.useState)(false);
741
+ const handleCopyText = (0, import_react4.useCallback)(async () => {
742
+ const lines = [
743
+ `Component: ${context.componentPath}`,
744
+ `Path: ${context.urlPath}`,
745
+ ...context.elementText ? [`Element: "${context.elementText}"`] : [],
746
+ ...comment.trim() ? [`
747
+ Comment: ${comment.trim()}`] : []
748
+ ];
749
+ try {
750
+ await navigator.clipboard.writeText(lines.join("\n"));
751
+ setCopiedText(true);
752
+ setTimeout(() => setCopiedText(false), 2e3);
753
+ } catch {
754
+ }
755
+ }, [context, comment]);
756
+ const handleCopyImage = (0, import_react4.useCallback)(async () => {
757
+ if (!screenshot) return;
758
+ try {
759
+ const blob = dataUrlToBlob(screenshot);
760
+ const pngBlob = blob.type === "image/png" ? blob : await new Promise((resolve) => {
761
+ const img = new Image();
762
+ img.onload = () => {
763
+ const canvas = document.createElement("canvas");
764
+ canvas.width = img.naturalWidth;
765
+ canvas.height = img.naturalHeight;
766
+ canvas.getContext("2d").drawImage(img, 0, 0);
767
+ canvas.toBlob((b) => resolve(b), "image/png");
768
+ };
769
+ img.src = screenshot;
770
+ });
771
+ await navigator.clipboard.write([
772
+ new ClipboardItem({ "image/png": pngBlob })
773
+ ]);
774
+ setCopiedImage(true);
775
+ setTimeout(() => setCopiedImage(false), 2e3);
776
+ } catch {
777
+ }
778
+ }, [screenshot]);
697
779
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
698
780
  "div",
699
781
  {
@@ -758,6 +840,44 @@ function DraftModal({ pendingContext, addingDraft, onAdd, onCancel }) {
758
840
  }
759
841
  }
760
842
  ),
843
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", gap: 8, marginBottom: 12 }, children: [
844
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
845
+ "button",
846
+ {
847
+ onClick: handleCopyText,
848
+ style: {
849
+ padding: "5px 12px",
850
+ borderRadius: 6,
851
+ border: "1px solid #d1d5db",
852
+ background: copiedText ? "#22c55e" : "#f3f4f6",
853
+ color: copiedText ? "#fff" : "#374151",
854
+ fontSize: 12,
855
+ fontWeight: 500,
856
+ cursor: "pointer",
857
+ transition: "background 0.15s, color 0.15s"
858
+ },
859
+ children: copiedText ? "Copied!" : "Copy Text"
860
+ }
861
+ ),
862
+ screenshot && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
863
+ "button",
864
+ {
865
+ onClick: handleCopyImage,
866
+ style: {
867
+ padding: "5px 12px",
868
+ borderRadius: 6,
869
+ border: "1px solid #d1d5db",
870
+ background: copiedImage ? "#22c55e" : "#f3f4f6",
871
+ color: copiedImage ? "#fff" : "#374151",
872
+ fontSize: 12,
873
+ fontWeight: 500,
874
+ cursor: "pointer",
875
+ transition: "background 0.15s, color 0.15s"
876
+ },
877
+ children: copiedImage ? "Copied!" : "Copy Image"
878
+ }
879
+ )
880
+ ] }),
761
881
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
762
882
  "textarea",
763
883
  {
@@ -1093,18 +1213,6 @@ ${propsStr}`);
1093
1213
  return lines.join("\n");
1094
1214
  }
1095
1215
 
1096
- // src/utils/blob.ts
1097
- function dataUrlToBlob(dataUrl) {
1098
- const [header, base64] = dataUrl.split(",");
1099
- const mime = header.match(/:(.*?);/)?.[1] || "image/png";
1100
- const binary = atob(base64);
1101
- const bytes = new Uint8Array(binary.length);
1102
- for (let i = 0; i < binary.length; i++) {
1103
- bytes[i] = binary.charCodeAt(i);
1104
- }
1105
- return new Blob([bytes], { type: mime });
1106
- }
1107
-
1108
1216
  // src/hooks/useSession.ts
1109
1217
  var import_react6 = require("react");
1110
1218
  var SESSION_TOKEN_KEY = "llm_feedback_session_token";
@@ -1402,7 +1510,6 @@ function FeedbackWidget({
1402
1510
  (0, import_react7.useEffect)(() => {
1403
1511
  if (session.status === "authenticated" && pendingOpenRef.current) {
1404
1512
  pendingOpenRef.current = false;
1405
- dispatch({ type: "OPEN_PANEL" });
1406
1513
  }
1407
1514
  }, [session.status]);
1408
1515
  (0, import_react7.useEffect)(() => {