@np-dev/ui-ai-anotation 0.1.0

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 (52) hide show
  1. package/README.md +245 -0
  2. package/dist/cjs/index.cjs +1550 -0
  3. package/dist/cjs/index.cjs.map +7 -0
  4. package/dist/cjs/index.native.cjs +1004 -0
  5. package/dist/cjs/index.native.cjs.map +7 -0
  6. package/dist/cjs/index.web.cjs +83 -0
  7. package/dist/cjs/index.web.cjs.map +7 -0
  8. package/dist/esm/index.js +1524 -0
  9. package/dist/esm/index.js.map +7 -0
  10. package/dist/esm/index.native.js +1012 -0
  11. package/dist/esm/index.native.js.map +7 -0
  12. package/dist/esm/index.web.js +62 -0
  13. package/dist/esm/index.web.js.map +7 -0
  14. package/dist/types/components/AnnotationInput.d.ts +8 -0
  15. package/dist/types/components/AnnotationList.d.ts +1 -0
  16. package/dist/types/components/Draggable.d.ts +10 -0
  17. package/dist/types/components/Highlighter.d.ts +1 -0
  18. package/dist/types/components/Toolbar.d.ts +1 -0
  19. package/dist/types/index.d.ts +20 -0
  20. package/dist/types/index.web.d.ts +69 -0
  21. package/dist/types/store.d.ts +66 -0
  22. package/dist/types/utils/fiber.d.ts +51 -0
  23. package/dist/types/utils/platform.d.ts +8 -0
  24. package/dist/types/utils/screenshot.d.ts +28 -0
  25. package/package.json +115 -0
  26. package/src/components/AnnotationInput.tsx +269 -0
  27. package/src/components/AnnotationList.tsx +248 -0
  28. package/src/components/Draggable.tsx +73 -0
  29. package/src/components/Highlighter.tsx +497 -0
  30. package/src/components/Toolbar.tsx +213 -0
  31. package/src/components/native/AnnotationInput.tsx +227 -0
  32. package/src/components/native/AnnotationList.tsx +157 -0
  33. package/src/components/native/Draggable.tsx +65 -0
  34. package/src/components/native/Highlighter.tsx +239 -0
  35. package/src/components/native/Toolbar.tsx +192 -0
  36. package/src/components/native/index.ts +6 -0
  37. package/src/components/web/AnnotationInput.tsx +150 -0
  38. package/src/components/web/AnnotationList.tsx +117 -0
  39. package/src/components/web/Draggable.tsx +74 -0
  40. package/src/components/web/Highlighter.tsx +329 -0
  41. package/src/components/web/Toolbar.tsx +198 -0
  42. package/src/components/web/index.ts +6 -0
  43. package/src/extension.tsx +15 -0
  44. package/src/index.native.tsx +50 -0
  45. package/src/index.tsx +41 -0
  46. package/src/index.web.tsx +124 -0
  47. package/src/store.tsx +120 -0
  48. package/src/utils/fiber.native.ts +90 -0
  49. package/src/utils/fiber.ts +255 -0
  50. package/src/utils/platform.ts +33 -0
  51. package/src/utils/screenshot.native.ts +139 -0
  52. package/src/utils/screenshot.ts +162 -0
@@ -0,0 +1,1550 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.tsx
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AiAnnotationProvider: () => AiAnnotationProvider2,
34
+ AnnotationInput: () => AnnotationInput,
35
+ AnnotationList: () => AnnotationList,
36
+ Draggable: () => Draggable,
37
+ Highlighter: () => Highlighter,
38
+ Toolbar: () => Toolbar,
39
+ captureScreenshot: () => captureScreenshot,
40
+ getComponentDisplayName: () => getComponentDisplayName,
41
+ getElementFromFiber: () => getElementFromFiber,
42
+ getPlatform: () => getPlatform,
43
+ getReactFiber: () => getReactFiber,
44
+ isNative: () => isNative,
45
+ isTauriEnv: () => isTauriEnv2,
46
+ isWeb: () => isWeb,
47
+ useAiAnnotation: () => useAiAnnotation
48
+ });
49
+ module.exports = __toCommonJS(index_exports);
50
+
51
+ // src/store.tsx
52
+ var import_react = require("react");
53
+ var import_jsx_runtime = require("react/jsx-runtime");
54
+ var initialState = {
55
+ mode: "disabled",
56
+ annotations: [],
57
+ hoveredElement: null,
58
+ hoveredComponentInfo: null,
59
+ isMinimized: false,
60
+ showList: false
61
+ };
62
+ var AnnotationContext = (0, import_react.createContext)(void 0);
63
+ function reducer(state, action) {
64
+ switch (action.type) {
65
+ case "SET_MODE":
66
+ return { ...state, mode: action.payload };
67
+ case "ADD_ANNOTATION":
68
+ return { ...state, annotations: [...state.annotations, action.payload] };
69
+ case "REMOVE_ANNOTATION":
70
+ return {
71
+ ...state,
72
+ annotations: state.annotations.filter((a) => a.id !== action.payload)
73
+ };
74
+ case "CLEAR_ALL_ANNOTATIONS":
75
+ return {
76
+ ...state,
77
+ annotations: []
78
+ };
79
+ case "SET_HOVERED":
80
+ if (state.hoveredElement === action.payload.element) return state;
81
+ return {
82
+ ...state,
83
+ hoveredElement: action.payload.element,
84
+ hoveredComponentInfo: action.payload.name ? { name: action.payload.name, details: action.payload.details } : null
85
+ };
86
+ case "RESET_HOVER":
87
+ return { ...state, hoveredElement: null, hoveredComponentInfo: null };
88
+ case "TOGGLE_MINIMIZED":
89
+ return { ...state, isMinimized: !state.isMinimized };
90
+ case "TOGGLE_LIST":
91
+ return { ...state, showList: !state.showList };
92
+ default:
93
+ return state;
94
+ }
95
+ }
96
+ function AiAnnotationProvider({ children }) {
97
+ const [state, dispatch] = (0, import_react.useReducer)(reducer, initialState);
98
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AnnotationContext.Provider, { value: { state, dispatch }, children });
99
+ }
100
+ function useAiAnnotation() {
101
+ const context = (0, import_react.useContext)(AnnotationContext);
102
+ if (!context) {
103
+ throw new Error("useAiAnnotation must be used within an AiAnnotationProvider");
104
+ }
105
+ return context;
106
+ }
107
+
108
+ // src/components/Toolbar.tsx
109
+ var import_react6 = require("react");
110
+
111
+ // src/components/Draggable.tsx
112
+ var import_react2 = require("react");
113
+ var import_jsx_runtime2 = require("react/jsx-runtime");
114
+ function Draggable({ children, initialPos = { x: 20, y: 20 } }) {
115
+ const [pos, setPos] = (0, import_react2.useState)(initialPos);
116
+ const [dragging, setDragging] = (0, import_react2.useState)(false);
117
+ const [rel, setRel] = (0, import_react2.useState)({ x: 0, y: 0 });
118
+ const nodeRef = (0, import_react2.useRef)(null);
119
+ const onMouseDown = (e) => {
120
+ if (e.button !== 0) return;
121
+ const node = nodeRef.current;
122
+ if (!node) return;
123
+ const rect = node.getBoundingClientRect();
124
+ setDragging(true);
125
+ setRel({
126
+ x: e.pageX - rect.left - window.scrollX,
127
+ y: e.pageY - rect.top - window.scrollY
128
+ });
129
+ e.preventDefault();
130
+ };
131
+ const onMouseMove = (e) => {
132
+ if (!dragging) return;
133
+ setPos({
134
+ x: e.pageX - rel.x,
135
+ y: e.pageY - rel.y
136
+ });
137
+ e.preventDefault();
138
+ };
139
+ const onMouseUp = () => {
140
+ setDragging(false);
141
+ };
142
+ (0, import_react2.useEffect)(() => {
143
+ if (dragging) {
144
+ document.addEventListener("mousemove", onMouseMove);
145
+ document.addEventListener("mouseup", onMouseUp);
146
+ } else {
147
+ document.removeEventListener("mousemove", onMouseMove);
148
+ document.removeEventListener("mouseup", onMouseUp);
149
+ }
150
+ return () => {
151
+ document.removeEventListener("mousemove", onMouseMove);
152
+ document.removeEventListener("mouseup", onMouseUp);
153
+ };
154
+ }, [dragging]);
155
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
156
+ "div",
157
+ {
158
+ ref: nodeRef,
159
+ style: {
160
+ position: "fixed",
161
+ left: pos.x,
162
+ top: pos.y,
163
+ zIndex: 9999,
164
+ cursor: dragging ? "grabbing" : "grab"
165
+ },
166
+ onMouseDown,
167
+ children
168
+ }
169
+ );
170
+ }
171
+
172
+ // src/components/Toolbar.tsx
173
+ var import_lucide_react3 = require("lucide-react");
174
+
175
+ // src/components/Highlighter.tsx
176
+ var import_react4 = require("react");
177
+
178
+ // src/components/AnnotationInput.tsx
179
+ var import_react3 = require("react");
180
+ var import_lucide_react = require("lucide-react");
181
+ var import_jsx_runtime3 = require("react/jsx-runtime");
182
+ function formatComponentDetails(details) {
183
+ const lines = [];
184
+ lines.push("## Element");
185
+ let elementStr = `<${details.elementInfo.tagName}`;
186
+ if (details.elementInfo.id) elementStr += ` id="${details.elementInfo.id}"`;
187
+ if (details.elementInfo.className) elementStr += ` class="${details.elementInfo.className}"`;
188
+ elementStr += ">";
189
+ lines.push(elementStr);
190
+ if (details.elementInfo.textContent) {
191
+ lines.push(`Text: "${details.elementInfo.textContent}"`);
192
+ }
193
+ lines.push(`Child elements: ${details.elementInfo.childElementCount}`);
194
+ if (Object.keys(details.elementInfo.attributes).length > 0) {
195
+ lines.push("");
196
+ lines.push("## Attributes");
197
+ for (const [key, value] of Object.entries(details.elementInfo.attributes)) {
198
+ lines.push(`${key}="${value}"`);
199
+ }
200
+ }
201
+ if (details.parentHierarchy.length > 0) {
202
+ lines.push("");
203
+ lines.push("## Parent Components");
204
+ lines.push(details.parentHierarchy.join(" \u2192 "));
205
+ }
206
+ if (details.childComponents.length > 0) {
207
+ lines.push("");
208
+ lines.push(`## Child Components (${details.childComponents.length})`);
209
+ const childLines = details.childComponents.slice(0, 15).map((child) => {
210
+ let str = child.name;
211
+ if (child.count > 1) str += ` \xD7${child.count}`;
212
+ if (child.hasChildren) str += " (has children)";
213
+ return str;
214
+ });
215
+ lines.push(childLines.join(", "));
216
+ if (details.childComponents.length > 15) {
217
+ lines.push(`... and ${details.childComponents.length - 15} more`);
218
+ }
219
+ }
220
+ return lines.join("\n");
221
+ }
222
+ function AnnotationInput({ onClose, componentName, componentDetails }) {
223
+ const { dispatch } = useAiAnnotation();
224
+ const [note, setNote] = (0, import_react3.useState)("");
225
+ const [includeDetails, setIncludeDetails] = (0, import_react3.useState)(true);
226
+ const [isVisible, setIsVisible] = (0, import_react3.useState)(false);
227
+ const [isClosing, setIsClosing] = (0, import_react3.useState)(false);
228
+ (0, import_react3.useEffect)(() => {
229
+ requestAnimationFrame(() => {
230
+ setIsVisible(true);
231
+ });
232
+ }, []);
233
+ (0, import_react3.useEffect)(() => {
234
+ const handleKeyDown = (e) => {
235
+ if (e.key === "Escape") {
236
+ e.preventDefault();
237
+ e.stopPropagation();
238
+ handleClose();
239
+ }
240
+ };
241
+ document.addEventListener("keydown", handleKeyDown);
242
+ return () => document.removeEventListener("keydown", handleKeyDown);
243
+ }, []);
244
+ const handleClose = () => {
245
+ setIsClosing(true);
246
+ setTimeout(() => {
247
+ onClose();
248
+ }, 200);
249
+ };
250
+ const handleSubmit = (e) => {
251
+ e.preventDefault();
252
+ if (!note.trim()) return;
253
+ let finalNote = note.trim();
254
+ if (includeDetails && componentDetails) {
255
+ const detailsText = formatComponentDetails(componentDetails);
256
+ finalNote = `${note.trim()}
257
+
258
+ ---
259
+ ${detailsText}`;
260
+ }
261
+ dispatch({
262
+ type: "ADD_ANNOTATION",
263
+ payload: {
264
+ id: Date.now().toString(),
265
+ componentName,
266
+ note: finalNote,
267
+ timestamp: Date.now(),
268
+ details: componentDetails
269
+ }
270
+ });
271
+ handleClose();
272
+ };
273
+ const detailsPreview = componentDetails ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
274
+ fontSize: "0.75rem",
275
+ color: "rgba(255, 255, 255, 0.6)",
276
+ marginTop: "4px"
277
+ }, children: [
278
+ componentDetails.parentHierarchy.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
279
+ "Parents: ",
280
+ componentDetails.parentHierarchy.slice(0, 3).join(" \u2192 "),
281
+ componentDetails.parentHierarchy.length > 3 ? "..." : ""
282
+ ] }),
283
+ componentDetails.childComponents.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
284
+ "Children: ",
285
+ componentDetails.childComponents.slice(0, 3).map((c) => c.name).join(", "),
286
+ componentDetails.childComponents.length > 3 ? "..." : ""
287
+ ] }),
288
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
289
+ "Element: <",
290
+ componentDetails.elementInfo.tagName,
291
+ "> (",
292
+ componentDetails.elementInfo.childElementCount,
293
+ " children)"
294
+ ] })
295
+ ] }) : null;
296
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
297
+ "div",
298
+ {
299
+ style: {
300
+ position: "fixed",
301
+ top: 0,
302
+ left: 0,
303
+ right: 0,
304
+ bottom: 0,
305
+ backgroundColor: isVisible && !isClosing ? "rgba(0,0,0,0.5)" : "rgba(0,0,0,0)",
306
+ display: "flex",
307
+ alignItems: "center",
308
+ justifyContent: "center",
309
+ zIndex: 1e4,
310
+ transition: "background-color 0.2s ease"
311
+ },
312
+ "data-ai-annotation-ui": true,
313
+ onClick: handleClose,
314
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
315
+ "div",
316
+ {
317
+ style: {
318
+ backgroundColor: "#1e1e1e",
319
+ color: "#e5e7eb",
320
+ padding: "20px",
321
+ borderRadius: "8px",
322
+ width: "500px",
323
+ maxWidth: "90%",
324
+ maxHeight: "80vh",
325
+ overflow: "auto",
326
+ boxShadow: "0 4px 6px rgba(0,0,0,0.3)",
327
+ transform: isVisible && !isClosing ? "scale(1) translateY(0)" : "scale(0.95) translateY(-10px)",
328
+ opacity: isVisible && !isClosing ? 1 : 0,
329
+ transition: "transform 0.2s ease, opacity 0.2s ease"
330
+ },
331
+ onClick: (e) => e.stopPropagation(),
332
+ children: [
333
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: "16px" }, children: [
334
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { style: { margin: 0, fontSize: "1.1rem" }, children: "Add Annotation" }),
335
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: handleClose, style: { background: "none", border: "none", color: "inherit", cursor: "pointer" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.X, { size: 20 }) })
336
+ ] }),
337
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginBottom: "12px", fontSize: "0.9rem", opacity: 0.8 }, children: [
338
+ "Component: ",
339
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: componentName })
340
+ ] }),
341
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { onSubmit: handleSubmit, children: [
342
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
343
+ "textarea",
344
+ {
345
+ value: note,
346
+ onChange: (e) => setNote(e.target.value),
347
+ placeholder: "Describe what this component does or what changes you need...",
348
+ style: {
349
+ width: "100%",
350
+ height: "120px",
351
+ padding: "8px",
352
+ borderRadius: "4px",
353
+ backgroundColor: "#2d2d2d",
354
+ border: "1px solid #404040",
355
+ color: "white",
356
+ marginBottom: "12px",
357
+ resize: "vertical",
358
+ fontFamily: "inherit"
359
+ },
360
+ autoFocus: true
361
+ }
362
+ ),
363
+ componentDetails && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { marginBottom: "16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
364
+ "label",
365
+ {
366
+ style: {
367
+ display: "flex",
368
+ alignItems: "flex-start",
369
+ gap: "8px",
370
+ cursor: "pointer",
371
+ fontSize: "0.85rem"
372
+ },
373
+ children: [
374
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
375
+ "input",
376
+ {
377
+ type: "checkbox",
378
+ checked: includeDetails,
379
+ onChange: (e) => setIncludeDetails(e.target.checked),
380
+ style: { marginTop: "2px" }
381
+ }
382
+ ),
383
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
384
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { color: "#10b981", fontWeight: 500 }, children: "Include component details in annotation" }),
385
+ includeDetails && detailsPreview
386
+ ] })
387
+ ]
388
+ }
389
+ ) }),
390
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", justifyContent: "flex-end", gap: "8px" }, children: [
391
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
392
+ "button",
393
+ {
394
+ type: "button",
395
+ onClick: handleClose,
396
+ style: {
397
+ padding: "6px 12px",
398
+ borderRadius: "4px",
399
+ backgroundColor: "transparent",
400
+ border: "1px solid #404040",
401
+ color: "white",
402
+ cursor: "pointer"
403
+ },
404
+ children: "Cancel"
405
+ }
406
+ ),
407
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
408
+ "button",
409
+ {
410
+ type: "submit",
411
+ style: {
412
+ padding: "6px 12px",
413
+ borderRadius: "4px",
414
+ backgroundColor: "#3b82f6",
415
+ border: "none",
416
+ color: "white",
417
+ cursor: "pointer"
418
+ },
419
+ children: "Save Annotation"
420
+ }
421
+ )
422
+ ] })
423
+ ] })
424
+ ]
425
+ }
426
+ )
427
+ }
428
+ );
429
+ }
430
+
431
+ // src/utils/screenshot.ts
432
+ var import_html2canvas = __toESM(require("html2canvas"));
433
+ function isTauriEnv() {
434
+ return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
435
+ }
436
+ async function copyToClipboardTauri(blob) {
437
+ if (typeof window.__TAURI_CLIPBOARD_WRITE_IMAGE__ === "function") {
438
+ const arrayBuffer = await blob.arrayBuffer();
439
+ const bytes = new Uint8Array(arrayBuffer);
440
+ await window.__TAURI_CLIPBOARD_WRITE_IMAGE__(bytes);
441
+ return;
442
+ }
443
+ throw new Error("Tauri clipboard function not available");
444
+ }
445
+ async function copyToClipboardBrowser(blob, dataUrl) {
446
+ try {
447
+ await navigator.clipboard.write([
448
+ new ClipboardItem({
449
+ "image/png": blob
450
+ })
451
+ ]);
452
+ } catch (clipboardError) {
453
+ console.warn("Failed to copy to clipboard:", clipboardError);
454
+ try {
455
+ await navigator.clipboard.writeText(dataUrl);
456
+ } catch {
457
+ }
458
+ }
459
+ }
460
+ async function captureScreenshot(element, options = {}) {
461
+ const {
462
+ scale = 2,
463
+ backgroundColor = null,
464
+ copyToClipboard = true,
465
+ download = false,
466
+ filename = "screenshot.png"
467
+ } = options;
468
+ try {
469
+ const canvas = await (0, import_html2canvas.default)(element, {
470
+ scale,
471
+ backgroundColor,
472
+ logging: false,
473
+ useCORS: true,
474
+ allowTaint: true,
475
+ // Capture just the element
476
+ x: window.scrollX,
477
+ y: window.scrollY,
478
+ scrollX: -window.scrollX,
479
+ scrollY: -window.scrollY,
480
+ windowWidth: document.documentElement.scrollWidth,
481
+ windowHeight: document.documentElement.scrollHeight,
482
+ // Ignore annotation UI elements
483
+ ignoreElements: (el) => {
484
+ return el.hasAttribute("data-ai-annotation-ui");
485
+ }
486
+ });
487
+ const blob = await new Promise((resolve, reject) => {
488
+ canvas.toBlob((b) => {
489
+ if (b) resolve(b);
490
+ else reject(new Error("Failed to create blob"));
491
+ }, "image/png");
492
+ });
493
+ const dataUrl = canvas.toDataURL("image/png");
494
+ if (copyToClipboard) {
495
+ if (isTauriEnv()) {
496
+ try {
497
+ await copyToClipboardTauri(blob);
498
+ } catch (tauriErr) {
499
+ console.warn("Tauri clipboard failed, falling back to browser API:", tauriErr);
500
+ await copyToClipboardBrowser(blob, dataUrl);
501
+ }
502
+ } else {
503
+ await copyToClipboardBrowser(blob, dataUrl);
504
+ }
505
+ }
506
+ if (download) {
507
+ const link = document.createElement("a");
508
+ link.href = dataUrl;
509
+ link.download = filename;
510
+ document.body.appendChild(link);
511
+ link.click();
512
+ document.body.removeChild(link);
513
+ }
514
+ return {
515
+ success: true,
516
+ dataUrl,
517
+ blob
518
+ };
519
+ } catch (error) {
520
+ const message = error instanceof Error ? error.message : "Unknown error";
521
+ console.error("Screenshot capture failed:", message);
522
+ return {
523
+ success: false,
524
+ error: message
525
+ };
526
+ }
527
+ }
528
+
529
+ // src/utils/fiber.ts
530
+ function getReactFiber(dom) {
531
+ const key = Object.keys(dom).find(
532
+ (key2) => key2.startsWith("__reactFiber$")
533
+ );
534
+ return key ? dom[key] : null;
535
+ }
536
+ function getComponentDisplayName(fiber) {
537
+ let curr = fiber;
538
+ while (curr) {
539
+ const name = curr.type?.displayName || curr.type?.name;
540
+ if (name && typeof curr.type === "function") return name;
541
+ curr = curr.return;
542
+ }
543
+ return "Unknown";
544
+ }
545
+ function getElementFromFiber(fiber) {
546
+ let curr = fiber;
547
+ while (curr) {
548
+ if (curr.stateNode instanceof HTMLElement) {
549
+ return curr.stateNode;
550
+ }
551
+ curr = curr.child;
552
+ }
553
+ return null;
554
+ }
555
+ function getParentHierarchy(fiber, maxDepth = 10) {
556
+ const hierarchy = [];
557
+ let curr = fiber?.return;
558
+ let depth = 0;
559
+ while (curr && depth < maxDepth) {
560
+ const name = curr.type?.displayName || curr.type?.name;
561
+ if (name && typeof curr.type === "function") {
562
+ if (!name.includes("$") && !name.startsWith("_")) {
563
+ hierarchy.push(name);
564
+ }
565
+ }
566
+ curr = curr.return;
567
+ depth++;
568
+ }
569
+ return hierarchy;
570
+ }
571
+ function getChildComponents(fiber, maxDepth = 5) {
572
+ const children = /* @__PURE__ */ new Map();
573
+ function traverse(node, depth) {
574
+ if (!node || depth > maxDepth) return;
575
+ const name = node.type?.displayName || node.type?.name;
576
+ if (name && typeof node.type === "function" && !name.includes("$") && !name.startsWith("_")) {
577
+ const existing = children.get(name);
578
+ const hasChildComponents = checkHasChildComponents(node.child);
579
+ if (existing) {
580
+ existing.count++;
581
+ if (depth < existing.depth) {
582
+ existing.depth = depth;
583
+ }
584
+ existing.hasChildren = existing.hasChildren || hasChildComponents;
585
+ } else {
586
+ children.set(name, { count: 1, depth, hasChildren: hasChildComponents });
587
+ }
588
+ }
589
+ if (node.child) traverse(node.child, depth + 1);
590
+ if (node.sibling) traverse(node.sibling, depth);
591
+ }
592
+ if (fiber?.child) {
593
+ traverse(fiber.child, 1);
594
+ }
595
+ return Array.from(children.entries()).map(([name, info]) => ({
596
+ name,
597
+ depth: info.depth,
598
+ count: info.count,
599
+ hasChildren: info.hasChildren
600
+ })).sort((a, b) => a.depth - b.depth);
601
+ }
602
+ function checkHasChildComponents(fiber, maxDepth = 3) {
603
+ if (!fiber || maxDepth <= 0) return false;
604
+ const name = fiber.type?.displayName || fiber.type?.name;
605
+ if (name && typeof fiber.type === "function" && !name.includes("$")) {
606
+ return true;
607
+ }
608
+ if (fiber.child && checkHasChildComponents(fiber.child, maxDepth - 1)) return true;
609
+ if (fiber.sibling && checkHasChildComponents(fiber.sibling, maxDepth - 1)) return true;
610
+ return false;
611
+ }
612
+ function getElementInfo(element) {
613
+ const importantAttributes = ["data-testid", "role", "aria-label", "type", "name", "href", "src"];
614
+ const attributes = {};
615
+ importantAttributes.forEach((attr) => {
616
+ const value = element.getAttribute(attr);
617
+ if (value) {
618
+ attributes[attr] = value;
619
+ }
620
+ });
621
+ let textContent;
622
+ const directText = Array.from(element.childNodes).filter((node) => node.nodeType === Node.TEXT_NODE).map((node) => node.textContent?.trim()).filter(Boolean).join(" ");
623
+ if (directText) {
624
+ textContent = directText.length > 100 ? directText.substring(0, 100) + "..." : directText;
625
+ }
626
+ return {
627
+ tagName: element.tagName.toLowerCase(),
628
+ id: element.id || void 0,
629
+ className: element.className && typeof element.className === "string" ? element.className.split(" ").slice(0, 5).join(" ") + (element.className.split(" ").length > 5 ? "..." : "") : void 0,
630
+ attributes,
631
+ childElementCount: element.childElementCount,
632
+ textContent
633
+ };
634
+ }
635
+ function getComponentProps(fiber, maxProps = 10) {
636
+ if (!fiber?.memoizedProps) return void 0;
637
+ const props = fiber.memoizedProps;
638
+ const safeProps = {};
639
+ let count = 0;
640
+ for (const key of Object.keys(props)) {
641
+ if (count >= maxProps) {
642
+ safeProps["..."] = `${Object.keys(props).length - maxProps} more props`;
643
+ break;
644
+ }
645
+ if (key === "children") continue;
646
+ const value = props[key];
647
+ const type = typeof value;
648
+ if (type === "function") {
649
+ safeProps[key] = "[Function]";
650
+ } else if (type === "object" && value !== null) {
651
+ if (Array.isArray(value)) {
652
+ safeProps[key] = `[Array(${value.length})]`;
653
+ } else if (value.$$typeof) {
654
+ safeProps[key] = "[ReactElement]";
655
+ } else {
656
+ try {
657
+ const json = JSON.stringify(value);
658
+ safeProps[key] = json.length > 100 ? "[Object]" : value;
659
+ } catch {
660
+ safeProps[key] = "[Object]";
661
+ }
662
+ }
663
+ } else {
664
+ safeProps[key] = value;
665
+ }
666
+ count++;
667
+ }
668
+ return Object.keys(safeProps).length > 0 ? safeProps : void 0;
669
+ }
670
+ function getComponentDetails(element, options) {
671
+ const { includeProps = false, maxParentDepth = 10, maxChildDepth = 5 } = options || {};
672
+ const fiber = getReactFiber(element);
673
+ const name = fiber ? getComponentDisplayName(fiber) : "Unknown";
674
+ let componentFiber = fiber;
675
+ while (componentFiber && typeof componentFiber.type !== "function") {
676
+ componentFiber = componentFiber.return;
677
+ }
678
+ return {
679
+ name,
680
+ parentHierarchy: getParentHierarchy(componentFiber, maxParentDepth),
681
+ childComponents: getChildComponents(componentFiber, maxChildDepth),
682
+ elementInfo: getElementInfo(element),
683
+ props: includeProps ? getComponentProps(componentFiber) : void 0
684
+ };
685
+ }
686
+
687
+ // src/components/Highlighter.tsx
688
+ var import_jsx_runtime4 = require("react/jsx-runtime");
689
+ function Highlighter() {
690
+ const { state, dispatch } = useAiAnnotation();
691
+ const { hoveredElement, mode, hoveredComponentInfo } = state;
692
+ const [rect, setRect] = (0, import_react4.useState)(null);
693
+ const [showInput, setShowInput] = (0, import_react4.useState)(false);
694
+ const [mousePos, setMousePos] = (0, import_react4.useState)({ x: 0, y: 0 });
695
+ const [isLocked, setIsLocked] = (0, import_react4.useState)(false);
696
+ const [lockedPos, setLockedPos] = (0, import_react4.useState)({ x: 0, y: 0 });
697
+ const [isCapturing, setIsCapturing] = (0, import_react4.useState)(false);
698
+ const [captureSuccess, setCaptureSuccess] = (0, import_react4.useState)(false);
699
+ const [showDetails, setShowDetails] = (0, import_react4.useState)(false);
700
+ const componentName = hoveredComponentInfo?.name || "";
701
+ const componentDetails = hoveredComponentInfo?.details;
702
+ (0, import_react4.useEffect)(() => {
703
+ if (mode !== "inspecting") return;
704
+ const handleMouseOver = (e) => {
705
+ if (isLocked) return;
706
+ const target = e.target;
707
+ if (target.closest("[data-ai-annotation-ui]") || target.closest("[data-annotation-tooltip]")) {
708
+ return;
709
+ }
710
+ const details = getComponentDetails(target, {
711
+ includeProps: false,
712
+ maxParentDepth: 10,
713
+ maxChildDepth: 5
714
+ });
715
+ dispatch({
716
+ type: "SET_HOVERED",
717
+ payload: { element: target, name: details.name, details }
718
+ });
719
+ };
720
+ document.addEventListener("mouseover", handleMouseOver);
721
+ return () => {
722
+ document.removeEventListener("mouseover", handleMouseOver);
723
+ };
724
+ }, [mode, isLocked, dispatch]);
725
+ (0, import_react4.useEffect)(() => {
726
+ if (mode !== "inspecting") return;
727
+ const handleGlobalEsc = (e) => {
728
+ if (e.key === "Escape" && !isLocked) {
729
+ e.preventDefault();
730
+ e.stopPropagation();
731
+ dispatch({ type: "SET_MODE", payload: "disabled" });
732
+ }
733
+ };
734
+ document.addEventListener("keydown", handleGlobalEsc);
735
+ return () => document.removeEventListener("keydown", handleGlobalEsc);
736
+ }, [mode, isLocked, dispatch]);
737
+ (0, import_react4.useEffect)(() => {
738
+ if (mode === "disabled") {
739
+ setRect(null);
740
+ setShowInput(false);
741
+ setIsLocked(false);
742
+ setMousePos({ x: 0, y: 0 });
743
+ setLockedPos({ x: 0, y: 0 });
744
+ setIsCapturing(false);
745
+ setCaptureSuccess(false);
746
+ setShowDetails(false);
747
+ dispatch({ type: "RESET_HOVER" });
748
+ }
749
+ }, [mode, dispatch]);
750
+ (0, import_react4.useEffect)(() => {
751
+ if (!hoveredElement) {
752
+ setRect(null);
753
+ setShowInput(false);
754
+ setIsLocked(false);
755
+ return;
756
+ }
757
+ const updateRect = () => {
758
+ const newRect = hoveredElement.getBoundingClientRect();
759
+ setRect(newRect);
760
+ };
761
+ updateRect();
762
+ const handleMouseMove = (e) => {
763
+ if (!isLocked) {
764
+ setMousePos({ x: e.clientX, y: e.clientY });
765
+ }
766
+ };
767
+ const handleClick = (e) => {
768
+ const target = e.target;
769
+ if (target.closest("[data-annotation-tooltip]")) {
770
+ return;
771
+ }
772
+ if (target.closest("[data-ai-annotation-ui]")) {
773
+ return;
774
+ }
775
+ if (!isLocked) {
776
+ e.preventDefault();
777
+ e.stopPropagation();
778
+ setIsLocked(true);
779
+ setLockedPos({ x: e.clientX, y: e.clientY });
780
+ } else {
781
+ setIsLocked(false);
782
+ }
783
+ };
784
+ const handleKeyDown = (e) => {
785
+ if (e.key === "Escape" && isLocked) {
786
+ e.preventDefault();
787
+ e.stopPropagation();
788
+ setIsLocked(false);
789
+ setShowInput(false);
790
+ }
791
+ };
792
+ window.addEventListener("scroll", updateRect, true);
793
+ window.addEventListener("resize", updateRect);
794
+ document.addEventListener("mousemove", handleMouseMove);
795
+ document.addEventListener("click", handleClick, true);
796
+ document.addEventListener("keydown", handleKeyDown);
797
+ return () => {
798
+ window.removeEventListener("scroll", updateRect, true);
799
+ window.removeEventListener("resize", updateRect);
800
+ document.removeEventListener("mousemove", handleMouseMove);
801
+ document.removeEventListener("click", handleClick, true);
802
+ document.removeEventListener("keydown", handleKeyDown);
803
+ };
804
+ }, [hoveredElement, isLocked]);
805
+ const handleScreenshot = async (e) => {
806
+ e.stopPropagation();
807
+ if (!hoveredElement || isCapturing) return;
808
+ setIsCapturing(true);
809
+ try {
810
+ const result = await captureScreenshot(hoveredElement, {
811
+ copyToClipboard: true,
812
+ scale: 2
813
+ });
814
+ if (result.success) {
815
+ setCaptureSuccess(true);
816
+ setTimeout(() => {
817
+ setCaptureSuccess(false);
818
+ setIsLocked(false);
819
+ }, 1500);
820
+ }
821
+ } catch (error) {
822
+ console.error("Screenshot failed:", error);
823
+ } finally {
824
+ setIsCapturing(false);
825
+ }
826
+ };
827
+ const handleAddClick = (e) => {
828
+ e.stopPropagation();
829
+ setShowInput(true);
830
+ setShowDetails(false);
831
+ setIsLocked(false);
832
+ };
833
+ const handleDetailsClick = (e) => {
834
+ e.stopPropagation();
835
+ setShowDetails(!showDetails);
836
+ };
837
+ const handleCloseInput = () => {
838
+ setShowInput(false);
839
+ };
840
+ if (mode !== "inspecting" || !rect) return null;
841
+ const tooltipX = isLocked ? lockedPos.x : mousePos.x;
842
+ const tooltipY = isLocked ? lockedPos.y : mousePos.y;
843
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
844
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
845
+ "div",
846
+ {
847
+ "data-annotation-ui": "true",
848
+ style: {
849
+ position: "fixed",
850
+ left: rect.left - 2,
851
+ top: rect.top - 2,
852
+ width: rect.width + 4,
853
+ height: rect.height + 4,
854
+ border: "2px solid #3b82f6",
855
+ borderRadius: 4,
856
+ pointerEvents: "none",
857
+ zIndex: 99998,
858
+ backgroundColor: "rgba(59, 130, 246, 0.1)"
859
+ }
860
+ }
861
+ ),
862
+ componentName && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
863
+ "div",
864
+ {
865
+ "data-annotation-ui": "true",
866
+ style: {
867
+ position: "fixed",
868
+ left: rect.left,
869
+ top: rect.top - 24,
870
+ backgroundColor: "#3b82f6",
871
+ color: "white",
872
+ padding: "2px 8px",
873
+ borderRadius: 4,
874
+ fontSize: 12,
875
+ fontFamily: "monospace",
876
+ pointerEvents: "none",
877
+ zIndex: 99999
878
+ },
879
+ children: componentName
880
+ }
881
+ ),
882
+ !showInput && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
883
+ "div",
884
+ {
885
+ "data-annotation-ui": "true",
886
+ "data-annotation-tooltip": "true",
887
+ style: {
888
+ position: "fixed",
889
+ left: tooltipX + 16,
890
+ top: tooltipY + 16,
891
+ display: "flex",
892
+ flexDirection: "column",
893
+ gap: 6,
894
+ padding: 6,
895
+ backgroundColor: isLocked ? "rgba(0, 0, 0, 0.95)" : "rgba(0, 0, 0, 0.85)",
896
+ borderRadius: 8,
897
+ boxShadow: isLocked ? "0 4px 12px rgba(0, 0, 0, 0.4), 0 0 0 2px rgba(59, 130, 246, 0.5)" : "0 4px 12px rgba(0, 0, 0, 0.3)",
898
+ zIndex: 1e5,
899
+ pointerEvents: isLocked ? "auto" : "none",
900
+ backdropFilter: "blur(8px)",
901
+ transition: isLocked ? "none" : "left 0.05s ease-out, top 0.05s ease-out",
902
+ maxWidth: showDetails ? 400 : "auto"
903
+ },
904
+ children: [
905
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", gap: 6 }, children: [
906
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
907
+ "button",
908
+ {
909
+ onClick: handleScreenshot,
910
+ disabled: isCapturing || !isLocked,
911
+ style: {
912
+ width: 32,
913
+ height: 32,
914
+ borderRadius: 6,
915
+ border: "none",
916
+ backgroundColor: captureSuccess ? "#22c55e" : isCapturing ? "#6b7280" : "#8b5cf6",
917
+ color: "white",
918
+ cursor: isLocked ? "pointer" : "default",
919
+ display: "flex",
920
+ alignItems: "center",
921
+ justifyContent: "center",
922
+ fontSize: 16,
923
+ transition: "all 0.15s ease",
924
+ opacity: isLocked ? 1 : 0.7
925
+ },
926
+ title: "Capture screenshot",
927
+ children: captureSuccess ? "\u2713" : isCapturing ? "..." : "\u{1F4F7}"
928
+ }
929
+ ),
930
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
931
+ "button",
932
+ {
933
+ onClick: handleAddClick,
934
+ disabled: !isLocked,
935
+ style: {
936
+ width: 32,
937
+ height: 32,
938
+ borderRadius: 6,
939
+ border: "none",
940
+ backgroundColor: "#3b82f6",
941
+ color: "white",
942
+ cursor: isLocked ? "pointer" : "default",
943
+ display: "flex",
944
+ alignItems: "center",
945
+ justifyContent: "center",
946
+ fontSize: 18,
947
+ fontWeight: "bold",
948
+ transition: "all 0.15s ease",
949
+ opacity: isLocked ? 1 : 0.7
950
+ },
951
+ title: "Add annotation",
952
+ children: "+"
953
+ }
954
+ ),
955
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
956
+ "button",
957
+ {
958
+ onClick: handleDetailsClick,
959
+ disabled: !isLocked,
960
+ style: {
961
+ width: 32,
962
+ height: 32,
963
+ borderRadius: 6,
964
+ border: showDetails ? "2px solid #10b981" : "none",
965
+ backgroundColor: showDetails ? "#10b981" : "#6b7280",
966
+ color: "white",
967
+ cursor: isLocked ? "pointer" : "default",
968
+ display: "flex",
969
+ alignItems: "center",
970
+ justifyContent: "center",
971
+ fontSize: 14,
972
+ transition: "all 0.15s ease",
973
+ opacity: isLocked ? 1 : 0.7
974
+ },
975
+ title: "Show component details",
976
+ children: "\u24D8"
977
+ }
978
+ )
979
+ ] }),
980
+ !isLocked && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
981
+ "div",
982
+ {
983
+ style: {
984
+ position: "absolute",
985
+ bottom: -20,
986
+ left: "50%",
987
+ transform: "translateX(-50%)",
988
+ fontSize: 10,
989
+ color: "rgba(255, 255, 255, 0.7)",
990
+ whiteSpace: "nowrap",
991
+ pointerEvents: "none"
992
+ },
993
+ children: "Click to lock"
994
+ }
995
+ ),
996
+ showDetails && componentDetails && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
997
+ "div",
998
+ {
999
+ style: {
1000
+ padding: "8px 4px 4px 4px",
1001
+ borderTop: "1px solid rgba(255, 255, 255, 0.2)",
1002
+ fontSize: 11,
1003
+ color: "rgba(255, 255, 255, 0.9)",
1004
+ maxHeight: 300,
1005
+ overflowY: "auto"
1006
+ },
1007
+ children: [
1008
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { marginBottom: 8 }, children: [
1009
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { color: "#60a5fa", fontWeight: "bold", marginBottom: 4 }, children: "Element" }),
1010
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { fontFamily: "monospace", color: "#f59e0b" }, children: [
1011
+ "<",
1012
+ componentDetails.elementInfo.tagName,
1013
+ componentDetails.elementInfo.id && ` id="${componentDetails.elementInfo.id}"`,
1014
+ componentDetails.elementInfo.className && ` class="${componentDetails.elementInfo.className}"`,
1015
+ ">"
1016
+ ] }),
1017
+ componentDetails.elementInfo.textContent && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { color: "rgba(255, 255, 255, 0.6)", marginTop: 2, fontStyle: "italic" }, children: [
1018
+ '"',
1019
+ componentDetails.elementInfo.textContent,
1020
+ '"'
1021
+ ] }),
1022
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { color: "rgba(255, 255, 255, 0.5)", marginTop: 2 }, children: [
1023
+ componentDetails.elementInfo.childElementCount,
1024
+ " child element(s)"
1025
+ ] })
1026
+ ] }),
1027
+ componentDetails.parentHierarchy.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { marginBottom: 8 }, children: [
1028
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { color: "#60a5fa", fontWeight: "bold", marginBottom: 4 }, children: "Parent Components" }),
1029
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { fontFamily: "monospace" }, children: [
1030
+ componentDetails.parentHierarchy.slice(0, 5).map((parent, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { color: "#a78bfa", paddingLeft: i * 8 }, children: [
1031
+ "\u2190 ",
1032
+ parent
1033
+ ] }, i)),
1034
+ componentDetails.parentHierarchy.length > 5 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { color: "rgba(255, 255, 255, 0.5)" }, children: [
1035
+ "... and ",
1036
+ componentDetails.parentHierarchy.length - 5,
1037
+ " more"
1038
+ ] })
1039
+ ] })
1040
+ ] }),
1041
+ componentDetails.childComponents.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { marginBottom: 8 }, children: [
1042
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { color: "#60a5fa", fontWeight: "bold", marginBottom: 4 }, children: [
1043
+ "Child Components (",
1044
+ componentDetails.childComponents.length,
1045
+ ")"
1046
+ ] }),
1047
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { fontFamily: "monospace" }, children: [
1048
+ componentDetails.childComponents.slice(0, 10).map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1049
+ "div",
1050
+ {
1051
+ style: {
1052
+ display: "flex",
1053
+ justifyContent: "space-between",
1054
+ gap: 8,
1055
+ color: "#34d399"
1056
+ },
1057
+ children: [
1058
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
1059
+ "\u2192 ",
1060
+ child.name,
1061
+ child.hasChildren && " \u25BE"
1062
+ ] }),
1063
+ child.count > 1 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { style: { color: "rgba(255, 255, 255, 0.5)" }, children: [
1064
+ "\xD7",
1065
+ child.count
1066
+ ] })
1067
+ ]
1068
+ },
1069
+ i
1070
+ )),
1071
+ componentDetails.childComponents.length > 10 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { color: "rgba(255, 255, 255, 0.5)" }, children: [
1072
+ "... and ",
1073
+ componentDetails.childComponents.length - 10,
1074
+ " more"
1075
+ ] })
1076
+ ] })
1077
+ ] }),
1078
+ Object.keys(componentDetails.elementInfo.attributes).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
1079
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { color: "#60a5fa", fontWeight: "bold", marginBottom: 4 }, children: "Attributes" }),
1080
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontFamily: "monospace" }, children: Object.entries(componentDetails.elementInfo.attributes).map(([key, value]) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { color: "#fbbf24" }, children: [
1081
+ key,
1082
+ '="',
1083
+ value,
1084
+ '"'
1085
+ ] }, key)) })
1086
+ ] })
1087
+ ]
1088
+ }
1089
+ )
1090
+ ]
1091
+ }
1092
+ ),
1093
+ showInput && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1094
+ AnnotationInput,
1095
+ {
1096
+ onClose: handleCloseInput,
1097
+ componentName: componentName || "Unknown",
1098
+ componentDetails
1099
+ }
1100
+ )
1101
+ ] });
1102
+ }
1103
+
1104
+ // src/components/AnnotationList.tsx
1105
+ var import_react5 = require("react");
1106
+ var import_lucide_react2 = require("lucide-react");
1107
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1108
+ function AnnotationList() {
1109
+ const { state, dispatch } = useAiAnnotation();
1110
+ const [expandedId, setExpandedId] = (0, import_react5.useState)(null);
1111
+ if (!state.showList) return null;
1112
+ const toggleExpand = (id) => {
1113
+ setExpandedId(expandedId === id ? null : id);
1114
+ };
1115
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1116
+ "div",
1117
+ {
1118
+ style: {
1119
+ position: "fixed",
1120
+ top: 0,
1121
+ left: 0,
1122
+ right: 0,
1123
+ bottom: 0,
1124
+ backgroundColor: "rgba(0,0,0,0.5)",
1125
+ display: "flex",
1126
+ alignItems: "center",
1127
+ justifyContent: "center",
1128
+ zIndex: 1e4
1129
+ },
1130
+ "data-ai-annotation-ui": true,
1131
+ onClick: () => dispatch({ type: "TOGGLE_LIST" }),
1132
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1133
+ "div",
1134
+ {
1135
+ style: {
1136
+ backgroundColor: "#1e1e1e",
1137
+ color: "#e5e7eb",
1138
+ padding: "20px",
1139
+ borderRadius: "8px",
1140
+ width: "600px",
1141
+ maxWidth: "90%",
1142
+ maxHeight: "80vh",
1143
+ display: "flex",
1144
+ flexDirection: "column",
1145
+ boxShadow: "0 4px 6px rgba(0,0,0,0.3)"
1146
+ },
1147
+ onClick: (e) => e.stopPropagation(),
1148
+ children: [
1149
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: "16px", alignItems: "center" }, children: [
1150
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("h3", { style: { margin: 0 }, children: [
1151
+ "Annotations (",
1152
+ state.annotations.length,
1153
+ ")"
1154
+ ] }),
1155
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
1156
+ state.annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1157
+ "button",
1158
+ {
1159
+ onClick: () => dispatch({ type: "CLEAR_ALL_ANNOTATIONS" }),
1160
+ style: {
1161
+ background: "#ef4444",
1162
+ border: "none",
1163
+ color: "white",
1164
+ cursor: "pointer",
1165
+ padding: "6px 12px",
1166
+ borderRadius: "4px",
1167
+ fontSize: "12px",
1168
+ display: "flex",
1169
+ alignItems: "center",
1170
+ gap: "4px"
1171
+ },
1172
+ title: "Clear all annotations",
1173
+ children: [
1174
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react2.Trash, { size: 14 }),
1175
+ "Clear All"
1176
+ ]
1177
+ }
1178
+ ),
1179
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1180
+ "button",
1181
+ {
1182
+ onClick: () => dispatch({ type: "TOGGLE_LIST" }),
1183
+ style: { background: "none", border: "none", color: "inherit", cursor: "pointer" },
1184
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react2.X, { size: 20 })
1185
+ }
1186
+ )
1187
+ ] })
1188
+ ] }),
1189
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { overflowY: "auto", flex: 1, display: "flex", flexDirection: "column", gap: "8px" }, children: state.annotations.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: { textAlign: "center", opacity: 0.6, padding: "20px" }, children: "No annotations yet." }) : state.annotations.map((ann) => {
1190
+ const isExpanded = expandedId === ann.id;
1191
+ const hasDetails = !!ann.details;
1192
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1193
+ "div",
1194
+ {
1195
+ style: {
1196
+ backgroundColor: "#2d2d2d",
1197
+ borderRadius: "4px",
1198
+ overflow: "hidden"
1199
+ },
1200
+ children: [
1201
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1202
+ "div",
1203
+ {
1204
+ style: {
1205
+ padding: "12px",
1206
+ display: "flex",
1207
+ justifyContent: "space-between",
1208
+ alignItems: "flex-start",
1209
+ gap: "12px"
1210
+ },
1211
+ children: [
1212
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { flex: 1 }, children: [
1213
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px", marginBottom: "4px" }, children: [
1214
+ hasDetails && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1215
+ "button",
1216
+ {
1217
+ onClick: () => toggleExpand(ann.id),
1218
+ style: {
1219
+ background: "none",
1220
+ border: "none",
1221
+ color: "#60a5fa",
1222
+ cursor: "pointer",
1223
+ padding: 0,
1224
+ display: "flex",
1225
+ alignItems: "center"
1226
+ },
1227
+ children: isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react2.ChevronDown, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react2.ChevronRight, { size: 16 })
1228
+ }
1229
+ ),
1230
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { fontWeight: "bold", fontSize: "0.9rem", color: "#60a5fa" }, children: ann.componentName })
1231
+ ] }),
1232
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { fontSize: "0.9rem", whiteSpace: "pre-wrap", marginLeft: hasDetails ? 24 : 0 }, children: ann.note })
1233
+ ] }),
1234
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1235
+ "button",
1236
+ {
1237
+ onClick: () => dispatch({ type: "REMOVE_ANNOTATION", payload: ann.id }),
1238
+ style: {
1239
+ background: "none",
1240
+ border: "none",
1241
+ color: "#ef4444",
1242
+ cursor: "pointer",
1243
+ padding: "4px",
1244
+ flexShrink: 0
1245
+ },
1246
+ title: "Remove annotation",
1247
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react2.Trash2, { size: 16 })
1248
+ }
1249
+ )
1250
+ ]
1251
+ }
1252
+ ),
1253
+ isExpanded && ann.details && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1254
+ "div",
1255
+ {
1256
+ style: {
1257
+ padding: "12px",
1258
+ paddingTop: 0,
1259
+ borderTop: "1px solid #404040",
1260
+ marginTop: "8px",
1261
+ fontSize: "0.8rem"
1262
+ },
1263
+ children: [
1264
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { marginBottom: "12px" }, children: [
1265
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { color: "#10b981", fontWeight: "bold", marginBottom: "4px" }, children: "Element" }),
1266
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { fontFamily: "monospace", color: "#f59e0b", fontSize: "0.75rem" }, children: [
1267
+ "<",
1268
+ ann.details.elementInfo.tagName,
1269
+ ann.details.elementInfo.id && ` id="${ann.details.elementInfo.id}"`,
1270
+ ann.details.elementInfo.className && ` class="${ann.details.elementInfo.className}"`,
1271
+ ">"
1272
+ ] }),
1273
+ ann.details.elementInfo.textContent && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { color: "rgba(255, 255, 255, 0.6)", marginTop: "4px", fontStyle: "italic" }, children: [
1274
+ 'Text: "',
1275
+ ann.details.elementInfo.textContent,
1276
+ '"'
1277
+ ] })
1278
+ ] }),
1279
+ ann.details.parentHierarchy.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { marginBottom: "12px" }, children: [
1280
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { color: "#10b981", fontWeight: "bold", marginBottom: "4px" }, children: "Parent Components" }),
1281
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { fontFamily: "monospace", fontSize: "0.75rem" }, children: ann.details.parentHierarchy.map((parent, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { color: "#a78bfa" }, children: [
1282
+ i > 0 && " \u2192 ",
1283
+ parent
1284
+ ] }, i)) })
1285
+ ] }),
1286
+ ann.details.childComponents.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { marginBottom: "12px" }, children: [
1287
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { color: "#10b981", fontWeight: "bold", marginBottom: "4px" }, children: [
1288
+ "Child Components (",
1289
+ ann.details.childComponents.length,
1290
+ ")"
1291
+ ] }),
1292
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: "6px" }, children: ann.details.childComponents.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1293
+ "span",
1294
+ {
1295
+ style: {
1296
+ backgroundColor: "#374151",
1297
+ padding: "2px 8px",
1298
+ borderRadius: "4px",
1299
+ fontFamily: "monospace",
1300
+ fontSize: "0.75rem",
1301
+ color: "#34d399"
1302
+ },
1303
+ children: [
1304
+ child.name,
1305
+ child.count > 1 && ` \xD7${child.count}`,
1306
+ child.hasChildren && " \u25BE"
1307
+ ]
1308
+ },
1309
+ i
1310
+ )) })
1311
+ ] }),
1312
+ Object.keys(ann.details.elementInfo.attributes).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
1313
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { color: "#10b981", fontWeight: "bold", marginBottom: "4px" }, children: "Attributes" }),
1314
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { fontFamily: "monospace", fontSize: "0.75rem" }, children: Object.entries(ann.details.elementInfo.attributes).map(([key, value]) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { color: "#fbbf24" }, children: [
1315
+ key,
1316
+ '="',
1317
+ value,
1318
+ '"'
1319
+ ] }, key)) })
1320
+ ] })
1321
+ ]
1322
+ }
1323
+ )
1324
+ ]
1325
+ },
1326
+ ann.id
1327
+ );
1328
+ }) })
1329
+ ]
1330
+ }
1331
+ )
1332
+ }
1333
+ );
1334
+ }
1335
+
1336
+ // src/components/Toolbar.tsx
1337
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1338
+ function Toolbar() {
1339
+ const { state, dispatch } = useAiAnnotation();
1340
+ const [showCopied, setShowCopied] = (0, import_react6.useState)(false);
1341
+ const [isAnimating, setIsAnimating] = (0, import_react6.useState)(false);
1342
+ const contentRef = (0, import_react6.useRef)(null);
1343
+ const [contentWidth, setContentWidth] = (0, import_react6.useState)(null);
1344
+ (0, import_react6.useEffect)(() => {
1345
+ if (contentRef.current && !state.isMinimized) {
1346
+ requestAnimationFrame(() => {
1347
+ if (contentRef.current) {
1348
+ setContentWidth(contentRef.current.scrollWidth);
1349
+ }
1350
+ });
1351
+ }
1352
+ }, [state.isMinimized, showCopied, state.annotations.length]);
1353
+ const handleToggleMinimized = () => {
1354
+ setIsAnimating(true);
1355
+ dispatch({ type: "TOGGLE_MINIMIZED" });
1356
+ setTimeout(() => setIsAnimating(false), 300);
1357
+ };
1358
+ const handleCopy = () => {
1359
+ if (state.annotations.length === 0) return;
1360
+ const sections = state.annotations.map((a, index) => {
1361
+ const lines = [];
1362
+ lines.push(`${"=".repeat(50)}`);
1363
+ lines.push(`Annotation ${index + 1}: ${a.componentName}`);
1364
+ lines.push(`${"=".repeat(50)}`);
1365
+ lines.push("");
1366
+ lines.push("## Instruction");
1367
+ lines.push(a.note);
1368
+ return lines.join("\n");
1369
+ });
1370
+ const text = sections.join("\n\n");
1371
+ navigator.clipboard.writeText(text).then(() => {
1372
+ setShowCopied(true);
1373
+ setTimeout(() => setShowCopied(false), 2e3);
1374
+ });
1375
+ };
1376
+ const toggleMode = () => {
1377
+ dispatch({
1378
+ type: "SET_MODE",
1379
+ payload: state.mode === "disabled" ? "inspecting" : "disabled"
1380
+ });
1381
+ };
1382
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1383
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Highlighter, {}),
1384
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AnnotationList, {}),
1385
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Draggable, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1386
+ "div",
1387
+ {
1388
+ style: {
1389
+ backgroundColor: "#1e1e1e",
1390
+ border: "1px solid #333",
1391
+ borderRadius: "8px",
1392
+ padding: "8px",
1393
+ boxShadow: "0 4px 12px rgba(0,0,0,0.3)",
1394
+ display: "flex",
1395
+ alignItems: "center",
1396
+ gap: "8px",
1397
+ color: "#e5e7eb",
1398
+ transition: "width 0.2s"
1399
+ },
1400
+ "data-ai-annotation-ui": true,
1401
+ children: [
1402
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { cursor: "grab", color: "#666", display: "flex" }, title: "Drag", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.GripVertical, { size: 20 }) }),
1403
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1404
+ "div",
1405
+ {
1406
+ ref: contentRef,
1407
+ style: {
1408
+ display: "flex",
1409
+ alignItems: "center",
1410
+ gap: "8px",
1411
+ overflow: "hidden",
1412
+ transition: "max-width 0.3s ease, opacity 0.2s ease",
1413
+ maxWidth: state.isMinimized ? 0 : contentWidth || 300,
1414
+ opacity: state.isMinimized ? 0 : 1,
1415
+ paddingTop: "4px",
1416
+ paddingBottom: "4px",
1417
+ marginTop: "-4px",
1418
+ marginBottom: "-4px"
1419
+ },
1420
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1421
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { width: "1px", height: "24px", backgroundColor: "#333" } }),
1422
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1423
+ "button",
1424
+ {
1425
+ onClick: toggleMode,
1426
+ title: state.mode === "inspecting" ? "Disable Inspection" : "Enable Inspection",
1427
+ style: {
1428
+ background: state.mode === "inspecting" ? "#3b82f6" : "transparent",
1429
+ border: "none",
1430
+ borderRadius: "4px",
1431
+ padding: "6px",
1432
+ color: state.mode === "inspecting" ? "white" : "inherit",
1433
+ cursor: "pointer",
1434
+ display: "flex"
1435
+ },
1436
+ children: state.mode === "inspecting" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.MousePointer2, { size: 18 }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Ban, { size: 18 })
1437
+ }
1438
+ ),
1439
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1440
+ "button",
1441
+ {
1442
+ onClick: () => dispatch({ type: "TOGGLE_LIST" }),
1443
+ title: "List Annotations",
1444
+ style: {
1445
+ background: "transparent",
1446
+ border: "none",
1447
+ borderRadius: "4px",
1448
+ padding: "6px",
1449
+ color: "inherit",
1450
+ cursor: "pointer",
1451
+ display: "flex",
1452
+ position: "relative"
1453
+ },
1454
+ children: [
1455
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.List, { size: 18 }),
1456
+ state.annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: {
1457
+ position: "absolute",
1458
+ top: -2,
1459
+ right: -2,
1460
+ background: "#ef4444",
1461
+ color: "white",
1462
+ fontSize: "9px",
1463
+ width: "14px",
1464
+ height: "14px",
1465
+ borderRadius: "50%",
1466
+ display: "flex",
1467
+ alignItems: "center",
1468
+ justifyContent: "center"
1469
+ }, children: state.annotations.length })
1470
+ ]
1471
+ }
1472
+ ),
1473
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1474
+ "button",
1475
+ {
1476
+ onClick: handleCopy,
1477
+ title: "Copy Annotations",
1478
+ style: {
1479
+ background: showCopied ? "#22c55e" : "transparent",
1480
+ border: "none",
1481
+ borderRadius: "4px",
1482
+ padding: "6px",
1483
+ color: showCopied ? "white" : "inherit",
1484
+ cursor: "pointer",
1485
+ display: "flex",
1486
+ alignItems: "center",
1487
+ gap: "4px",
1488
+ transition: "background-color 0.2s, color 0.2s",
1489
+ minWidth: showCopied ? "75px" : "auto"
1490
+ },
1491
+ children: showCopied ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1492
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Check, { size: 18 }),
1493
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontSize: "12px", whiteSpace: "nowrap" }, children: "Copied!" })
1494
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Copy, { size: 18 })
1495
+ }
1496
+ )
1497
+ ] })
1498
+ }
1499
+ ),
1500
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { width: "1px", height: "24px", backgroundColor: "#333", marginLeft: state.isMinimized ? 0 : "auto" } }),
1501
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1502
+ "button",
1503
+ {
1504
+ onClick: handleToggleMinimized,
1505
+ title: state.isMinimized ? "Expand" : "Minimize",
1506
+ style: {
1507
+ background: "transparent",
1508
+ border: "none",
1509
+ borderRadius: "4px",
1510
+ padding: "6px",
1511
+ color: "inherit",
1512
+ cursor: "pointer",
1513
+ display: "flex"
1514
+ },
1515
+ children: state.isMinimized ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Maximize2, { size: 18 }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Minus, { size: 18 })
1516
+ }
1517
+ )
1518
+ ]
1519
+ }
1520
+ ) })
1521
+ ] });
1522
+ }
1523
+
1524
+ // src/utils/platform.ts
1525
+ var isWeb = typeof document !== "undefined" && typeof window !== "undefined";
1526
+ var isNative = !isWeb && typeof global !== "undefined";
1527
+ function getPlatform() {
1528
+ if (isWeb) return "web";
1529
+ try {
1530
+ const { Platform } = require("react-native");
1531
+ if (Platform.OS === "ios") return "ios";
1532
+ if (Platform.OS === "android") return "android";
1533
+ return "native";
1534
+ } catch {
1535
+ return "native";
1536
+ }
1537
+ }
1538
+ function isTauriEnv2() {
1539
+ return isWeb && typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
1540
+ }
1541
+
1542
+ // src/index.tsx
1543
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1544
+ function AiAnnotationProvider2({ children }) {
1545
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(AiAnnotationProvider, { children: [
1546
+ children,
1547
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Toolbar, {})
1548
+ ] });
1549
+ }
1550
+ //# sourceMappingURL=index.cjs.map