@david-xpn/llm-ui-feedback 0.1.1 → 0.1.3

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.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/components/FeedbackWidget.tsx
2
- import { useReducer, useCallback as useCallback3, useEffect as useEffect3, useMemo, useRef as useRef3 } from "react";
2
+ import { useReducer, useCallback as useCallback3, useEffect as useEffect4, useMemo, useRef as useRef3 } from "react";
3
3
  import { createPortal } from "react-dom";
4
4
 
5
5
  // src/utils/color.ts
@@ -89,7 +89,7 @@ function FloatingButton({ onPickClick, onPanelToggle, draftCount, panelOpen, pos
89
89
  }
90
90
 
91
91
  // src/components/PickOverlay.tsx
92
- import { useCallback, useEffect, useRef, useState } from "react";
92
+ import { useEffect, useRef, useState } from "react";
93
93
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
94
94
  var WIDGET_CONTAINER_ID = "llm-ui-feedback-root";
95
95
  function getElementsBeneathRect(rect) {
@@ -100,9 +100,7 @@ function getElementsBeneathRect(rect) {
100
100
  for (let x = rect.x; x <= rect.x + rect.width; x += step) {
101
101
  for (let y = rect.y; y <= rect.y + rect.height; y += step) {
102
102
  const el = document.elementFromPoint(x, y);
103
- if (el && !seen.has(el)) {
104
- seen.add(el);
105
- }
103
+ if (el && !seen.has(el)) seen.add(el);
106
104
  }
107
105
  }
108
106
  for (const [x, y] of [
@@ -118,55 +116,95 @@ function getElementsBeneathRect(rect) {
118
116
  if (container) container.style.display = "";
119
117
  return Array.from(seen).filter((el) => !container?.contains(el));
120
118
  }
119
+ function getElementAtPoint(x, y) {
120
+ const container = document.getElementById(WIDGET_CONTAINER_ID);
121
+ if (container) container.style.display = "none";
122
+ const el = document.elementFromPoint(x, y);
123
+ if (container) container.style.display = "";
124
+ if (el && container?.contains(el)) return null;
125
+ return el;
126
+ }
127
+ var DRAG_THRESHOLD = 10;
121
128
  function PickOverlay({ onPick, onCancel }) {
122
- const [dragging, setDragging] = useState(false);
123
- const [start, setStart] = useState(null);
124
- const [current, setCurrent] = useState(null);
125
- const overlayRef = useRef(null);
126
- const handleMouseDown = useCallback((e) => {
127
- if (e.button !== 0) return;
128
- e.preventDefault();
129
- e.stopPropagation();
130
- setStart({ x: e.clientX, y: e.clientY });
131
- setCurrent({ x: e.clientX, y: e.clientY });
132
- setDragging(true);
133
- }, []);
134
- const handleMouseMove = useCallback((e) => {
135
- if (!dragging) return;
136
- e.preventDefault();
137
- setCurrent({ x: e.clientX, y: e.clientY });
138
- }, [dragging]);
139
- const handleMouseUp = useCallback((e) => {
140
- if (!dragging || !start) return;
141
- e.preventDefault();
142
- e.stopPropagation();
143
- setDragging(false);
144
- const endX = e.clientX;
145
- const endY = e.clientY;
146
- const x = Math.min(start.x, endX);
147
- const y = Math.min(start.y, endY);
148
- const width = Math.abs(endX - start.x);
149
- const height = Math.abs(endY - start.y);
150
- if (width < 10 || height < 10) {
151
- setStart(null);
152
- setCurrent(null);
153
- return;
129
+ const activeRef = useRef(false);
130
+ const startRef = useRef(null);
131
+ const isDragRef = useRef(false);
132
+ const [hoverRect, setHoverRect] = useState(null);
133
+ const [selRect, setSelRect] = useState(null);
134
+ const onPickRef = useRef(onPick);
135
+ onPickRef.current = onPick;
136
+ const onCancelRef = useRef(onCancel);
137
+ onCancelRef.current = onCancel;
138
+ useEffect(() => {
139
+ function handleMouseMove(e) {
140
+ if (activeRef.current && startRef.current) {
141
+ const dx = Math.abs(e.clientX - startRef.current.x);
142
+ const dy = Math.abs(e.clientY - startRef.current.y);
143
+ if (dx >= DRAG_THRESHOLD || dy >= DRAG_THRESHOLD) {
144
+ isDragRef.current = true;
145
+ }
146
+ if (isDragRef.current) {
147
+ setHoverRect(null);
148
+ setSelRect({
149
+ left: Math.min(startRef.current.x, e.clientX),
150
+ top: Math.min(startRef.current.y, e.clientY),
151
+ width: Math.abs(e.clientX - startRef.current.x),
152
+ height: Math.abs(e.clientY - startRef.current.y)
153
+ });
154
+ }
155
+ e.preventDefault();
156
+ } else {
157
+ const el = getElementAtPoint(e.clientX, e.clientY);
158
+ if (el) {
159
+ const r = el.getBoundingClientRect();
160
+ setHoverRect({ x: r.left, y: r.top, width: r.width, height: r.height });
161
+ } else {
162
+ setHoverRect(null);
163
+ }
164
+ }
154
165
  }
155
- const rect = { x, y, width, height };
156
- const elements = getElementsBeneathRect(rect);
157
- setStart(null);
158
- setCurrent(null);
159
- onPick(rect, elements);
160
- }, [dragging, start, onPick]);
161
- const handleKeyDown = useCallback((e) => {
162
- if (e.key === "Escape") {
163
- setDragging(false);
164
- setStart(null);
165
- setCurrent(null);
166
- onCancel();
166
+ function handleMouseDown(e) {
167
+ if (e.button !== 0) return;
168
+ e.preventDefault();
169
+ e.stopPropagation();
170
+ startRef.current = { x: e.clientX, y: e.clientY };
171
+ isDragRef.current = false;
172
+ activeRef.current = true;
173
+ }
174
+ function handleMouseUp(e) {
175
+ if (!activeRef.current || !startRef.current) return;
176
+ e.preventDefault();
177
+ e.stopPropagation();
178
+ const start = startRef.current;
179
+ activeRef.current = false;
180
+ startRef.current = null;
181
+ setSelRect(null);
182
+ setHoverRect(null);
183
+ if (!isDragRef.current) {
184
+ const el = getElementAtPoint(e.clientX, e.clientY);
185
+ if (el) {
186
+ const r = el.getBoundingClientRect();
187
+ onPickRef.current({ x: r.left, y: r.top, width: r.width, height: r.height }, [el]);
188
+ }
189
+ return;
190
+ }
191
+ const x = Math.min(start.x, e.clientX);
192
+ const y = Math.min(start.y, e.clientY);
193
+ const width = Math.abs(e.clientX - start.x);
194
+ const height = Math.abs(e.clientY - start.y);
195
+ const rect = { x, y, width, height };
196
+ const elements = getElementsBeneathRect(rect);
197
+ onPickRef.current(rect, elements);
198
+ }
199
+ function handleKeyDown(e) {
200
+ if (e.key === "Escape") {
201
+ activeRef.current = false;
202
+ startRef.current = null;
203
+ setSelRect(null);
204
+ setHoverRect(null);
205
+ onCancelRef.current();
206
+ }
167
207
  }
168
- }, [onCancel]);
169
- useEffect(() => {
170
208
  document.addEventListener("mousedown", handleMouseDown, true);
171
209
  document.addEventListener("mousemove", handleMouseMove, true);
172
210
  document.addEventListener("mouseup", handleMouseUp, true);
@@ -177,21 +215,11 @@ function PickOverlay({ onPick, onCancel }) {
177
215
  document.removeEventListener("mouseup", handleMouseUp, true);
178
216
  document.removeEventListener("keydown", handleKeyDown, true);
179
217
  };
180
- }, [handleMouseDown, handleMouseMove, handleMouseUp, handleKeyDown]);
181
- let selRect = null;
182
- if (start && current && dragging) {
183
- selRect = {
184
- left: Math.min(start.x, current.x),
185
- top: Math.min(start.y, current.y),
186
- width: Math.abs(current.x - start.x),
187
- height: Math.abs(current.y - start.y)
188
- };
189
- }
218
+ }, []);
190
219
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
191
220
  /* @__PURE__ */ jsx2(
192
221
  "div",
193
222
  {
194
- ref: overlayRef,
195
223
  style: {
196
224
  position: "fixed",
197
225
  top: 0,
@@ -221,12 +249,30 @@ function PickOverlay({ onPick, onCancel }) {
221
249
  pointerEvents: "none"
222
250
  },
223
251
  children: [
224
- "Drag to select an area for feedback. Press ",
252
+ "Click an element or drag to select an area. Press ",
225
253
  /* @__PURE__ */ jsx2("strong", { children: "Esc" }),
226
254
  " to cancel."
227
255
  ]
228
256
  }
229
257
  ),
258
+ hoverRect && /* @__PURE__ */ jsx2(
259
+ "div",
260
+ {
261
+ style: {
262
+ position: "fixed",
263
+ left: hoverRect.x,
264
+ top: hoverRect.y,
265
+ width: hoverRect.width,
266
+ height: hoverRect.height,
267
+ border: "2px solid #3b82f6",
268
+ background: "rgba(59, 130, 246, 0.08)",
269
+ zIndex: 99998,
270
+ pointerEvents: "none",
271
+ borderRadius: 2,
272
+ transition: "all 0.1s ease-out"
273
+ }
274
+ }
275
+ ),
230
276
  selRect && /* @__PURE__ */ jsx2(
231
277
  "div",
232
278
  {
@@ -585,10 +631,27 @@ function SidePanel({
585
631
  }
586
632
 
587
633
  // src/components/DraftModal.tsx
588
- import { useState as useState4 } from "react";
634
+ import { useState as useState4, useEffect as useEffect2 } from "react";
589
635
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
636
+ var WIDGET_CONTAINER_ID2 = "llm-ui-feedback-root";
590
637
  function DraftModal({ pendingContext, addingDraft, onAdd, onCancel }) {
591
638
  const [comment, setComment] = useState4("");
639
+ useEffect2(() => {
640
+ const container = document.getElementById(WIDGET_CONTAINER_ID2);
641
+ const inerted = [];
642
+ for (const child of Array.from(document.body.children)) {
643
+ if (child === container || child.id === WIDGET_CONTAINER_ID2) continue;
644
+ if (!child.hasAttribute("inert")) {
645
+ child.setAttribute("inert", "");
646
+ inerted.push(child);
647
+ }
648
+ }
649
+ return () => {
650
+ for (const el of inerted) {
651
+ el.removeAttribute("inert");
652
+ }
653
+ };
654
+ }, []);
592
655
  const handleAdd = () => {
593
656
  if (comment.trim() && !addingDraft) {
594
657
  onAdd(comment.trim());
@@ -937,10 +1000,10 @@ function dataUrlToBlob(dataUrl) {
937
1000
  }
938
1001
 
939
1002
  // src/hooks/useSession.ts
940
- import { useState as useState6, useEffect as useEffect2, useCallback as useCallback2, useRef as useRef2 } from "react";
1003
+ import { useState as useState6, useEffect as useEffect3, useCallback as useCallback2, useRef as useRef2 } from "react";
941
1004
  var SESSION_TOKEN_KEY = "llm_feedback_session_token";
942
1005
  var USER_KEY = "llm_feedback_user";
943
- var AUTH_BYPASS = typeof import.meta !== "undefined" && import.meta.env?.VITE_AUTH_BYPASS === "true";
1006
+ var AUTH_BYPASS = !!(typeof globalThis !== "undefined" && globalThis.__FEEDBACK_AUTH_BYPASS__);
944
1007
  function getSessionToken() {
945
1008
  if (AUTH_BYPASS) return "bypass-token";
946
1009
  return localStorage.getItem(SESSION_TOKEN_KEY);
@@ -955,7 +1018,7 @@ function useSession(apiUrl, clientId) {
955
1018
  const [status, setStatus] = useState6(AUTH_BYPASS ? "authenticated" : "loading");
956
1019
  const [user, setUser] = useState6(AUTH_BYPASS ? BYPASS_USER : null);
957
1020
  const pendingAuthRef = useRef2(false);
958
- useEffect2(() => {
1021
+ useEffect3(() => {
959
1022
  if (AUTH_BYPASS) return;
960
1023
  let cancelled = false;
961
1024
  async function checkSession() {
@@ -996,7 +1059,7 @@ function useSession(apiUrl, clientId) {
996
1059
  cancelled = true;
997
1060
  };
998
1061
  }, [apiUrl]);
999
- useEffect2(() => {
1062
+ useEffect3(() => {
1000
1063
  function handleMessage(event) {
1001
1064
  if (event.data?.type !== "feedback-auth") return;
1002
1065
  const { sessionToken, user: userData } = event.data;
@@ -1229,13 +1292,13 @@ function FeedbackWidget({
1229
1292
  () => createApiClient(apiUrl, clientId),
1230
1293
  [apiUrl, clientId]
1231
1294
  );
1232
- useEffect3(() => {
1295
+ useEffect4(() => {
1233
1296
  if (session.status === "authenticated" && pendingOpenRef.current) {
1234
1297
  pendingOpenRef.current = false;
1235
1298
  dispatch({ type: "OPEN_PANEL" });
1236
1299
  }
1237
1300
  }, [session.status]);
1238
- useEffect3(() => {
1301
+ useEffect4(() => {
1239
1302
  if (session.status === "authenticated") {
1240
1303
  api.fetchDrafts().then((drafts) => {
1241
1304
  dispatch({ type: "SET_DRAFTS", drafts });
@@ -1243,7 +1306,7 @@ function FeedbackWidget({
1243
1306
  });
1244
1307
  }
1245
1308
  }, [state.panelOpen, session.status, api]);
1246
- useEffect3(() => {
1309
+ useEffect4(() => {
1247
1310
  if (!hotkey) return;
1248
1311
  const parsed = parseHotkey(hotkey);
1249
1312
  function handler(e) {
@@ -1303,10 +1366,13 @@ function FeedbackWidget({
1303
1366
  clickX: centerX,
1304
1367
  clickY: centerY
1305
1368
  };
1369
+ const isDrag = rect.width > 20 && rect.height > 20;
1306
1370
  let screenshot = null;
1307
- try {
1308
- screenshot = await captureScreenshot(rect);
1309
- } catch {
1371
+ if (isDrag) {
1372
+ try {
1373
+ screenshot = await captureScreenshot(rect);
1374
+ } catch {
1375
+ }
1310
1376
  }
1311
1377
  dispatch({ type: "ELEMENT_PICKED", context, screenshot });
1312
1378
  }, []);