glitchgrab 1.44.0 → 1.45.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.
package/dist/index.mjs CHANGED
@@ -6,8 +6,8 @@ import {
6
6
  useContext,
7
7
  useRef as useRef5,
8
8
  useEffect as useEffect6,
9
- useCallback as useCallback2,
10
- useMemo as useMemo3,
9
+ useCallback as useCallback3,
10
+ useMemo as useMemo4,
11
11
  useState as useState6
12
12
  } from "react";
13
13
 
@@ -711,7 +711,9 @@ async function assistTurn(params, token, baseUrl) {
711
711
  conversationId: null,
712
712
  question: null,
713
713
  report: null,
714
- degraded: (_a = envelope == null ? void 0 : envelope.error) != null ? _a : offline.degraded
714
+ degraded: (_a = envelope == null ? void 0 : envelope.error) != null ? _a : offline.degraded,
715
+ // An hourly limit, not a cap: the dialog keeps the assistant button.
716
+ retryable: (envelope == null ? void 0 : envelope.retryable) === true
715
717
  };
716
718
  }
717
719
  return {
@@ -944,15 +946,15 @@ import {
944
946
  useState as useState4,
945
947
  useRef as useRef4,
946
948
  useEffect as useEffect4,
947
- useMemo
949
+ useMemo as useMemo2
948
950
  } from "react";
949
951
  import { createPortal as createPortal3 } from "react-dom";
950
952
  import { useEffect, useRef, useState } from "react";
951
953
  import { jsx, jsxs } from "react/jsx-runtime";
952
- import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
954
+ import { useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
953
955
  import { createPortal } from "react-dom";
954
956
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
955
- import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
957
+ import { useCallback, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
956
958
  import { createPortal as createPortal2 } from "react-dom";
957
959
  import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
958
960
  import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
@@ -1313,6 +1315,59 @@ var SEVERITY_LABELS = {
1313
1315
  };
1314
1316
  var MAX_ASSIST_IMAGES = 3;
1315
1317
  var MAX_ASSIST_IMAGE_CHARS = 35e5;
1318
+ var MAX_ASSIST_FILES = 3;
1319
+ var MAX_ASSIST_FILE_CHARS = 3e5;
1320
+ var MAX_ASSIST_FILE_CHARS_EACH = 2e5;
1321
+ var TEXT_ATTACHMENT_EXTENSIONS = [
1322
+ ".html",
1323
+ ".htm",
1324
+ ".txt",
1325
+ ".md",
1326
+ ".json",
1327
+ ".xml",
1328
+ ".log",
1329
+ ".yml",
1330
+ ".yaml",
1331
+ ".csv",
1332
+ ".har",
1333
+ ".js",
1334
+ ".mjs",
1335
+ ".cjs",
1336
+ ".ts",
1337
+ ".tsx",
1338
+ ".jsx",
1339
+ ".css",
1340
+ ".scss",
1341
+ ".py",
1342
+ ".sh"
1343
+ ];
1344
+ var BINARY_ATTACHMENT_EXTENSIONS = [".pdf", ".doc", ".docx", ".xls", ".xlsx", ".zip"];
1345
+ var ATTACHMENT_ACCEPT = [
1346
+ "image/*",
1347
+ ...TEXT_ATTACHMENT_EXTENSIONS,
1348
+ ...BINARY_ATTACHMENT_EXTENSIONS
1349
+ ].join(",");
1350
+ var ASSIST_ATTACHMENT_ACCEPT = ["image/*", ...TEXT_ATTACHMENT_EXTENSIONS].join(",");
1351
+ function isAssistReadableFile(name) {
1352
+ const lower = name.toLowerCase();
1353
+ return TEXT_ATTACHMENT_EXTENSIONS.some((ext) => lower.endsWith(ext));
1354
+ }
1355
+ function decodeTextDataUrl(dataUrl) {
1356
+ try {
1357
+ if (!dataUrl.startsWith("data:")) return null;
1358
+ const comma = dataUrl.indexOf(",");
1359
+ if (comma < 0) return null;
1360
+ const meta = dataUrl.slice(5, comma);
1361
+ const payload = dataUrl.slice(comma + 1);
1362
+ if (!meta.endsWith(";base64")) return decodeURIComponent(payload);
1363
+ const binary = atob(payload);
1364
+ const bytes = new Uint8Array(binary.length);
1365
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
1366
+ return new TextDecoder("utf-8").decode(bytes);
1367
+ } catch (e) {
1368
+ return null;
1369
+ }
1370
+ }
1316
1371
  function getTypeLabel(type) {
1317
1372
  switch (type) {
1318
1373
  case "RATING":
@@ -1344,6 +1399,22 @@ function pickAssistImages(all) {
1344
1399
  }
1345
1400
  return picked;
1346
1401
  }
1402
+ var NO_FILES = [];
1403
+ function pickAssistFiles(files) {
1404
+ const picked = [];
1405
+ let total = 0;
1406
+ for (let i = files.length - 1; i >= 0 && picked.length < MAX_ASSIST_FILES; i--) {
1407
+ const attached = files[i];
1408
+ if (!isAssistReadableFile(attached.name)) continue;
1409
+ const text = decodeTextDataUrl(attached.dataUrl);
1410
+ if (!text || !text.trim()) continue;
1411
+ const content = text.slice(0, MAX_ASSIST_FILE_CHARS_EACH);
1412
+ if (total + content.length > MAX_ASSIST_FILE_CHARS) continue;
1413
+ picked.unshift({ index: i, file: { name: attached.name, content } });
1414
+ total += content.length;
1415
+ }
1416
+ return picked;
1417
+ }
1347
1418
  var STARTERS = [
1348
1419
  "Something on this page is broken",
1349
1420
  "It did the wrong thing",
@@ -1420,7 +1491,9 @@ function AssistSheet({
1420
1491
  theme: t2,
1421
1492
  screenshots,
1422
1493
  pageShot = null,
1423
- onAddImages,
1494
+ onAddFiles,
1495
+ files = NO_FILES,
1496
+ onRemoveFile,
1424
1497
  onRemoveImage,
1425
1498
  attachmentCount,
1426
1499
  context,
@@ -1491,6 +1564,8 @@ function AssistSheet({
1491
1564
  }
1492
1565
  }
1493
1566
  const readShots = new Set(pickAssistImages(screenshots));
1567
+ const sentFiles = useMemo(() => pickAssistFiles(files), [files]);
1568
+ const readFiles = new Set(sentFiles.map((f) => f.index));
1494
1569
  function leave(type, detail) {
1495
1570
  logEvent(type, detail);
1496
1571
  flushEvents();
@@ -1504,10 +1579,10 @@ function AssistSheet({
1504
1579
  var _a;
1505
1580
  if (phase === "draft") (_a = draftRef.current) == null ? void 0 : _a.focus();
1506
1581
  }, [phase]);
1507
- function degradeKeepingEvents(events, message) {
1582
+ function degradeKeepingEvents(events, message, retryable) {
1508
1583
  if (events) eventsRef.current = [...events, ...eventsRef.current];
1509
1584
  flushEvents();
1510
- onDegrade(message);
1585
+ onDegrade(message, retryable);
1511
1586
  }
1512
1587
  async function runTurn(history2) {
1513
1588
  var _a, _b, _c, _d;
@@ -1525,6 +1600,9 @@ function AssistSheet({
1525
1600
  ),
1526
1601
  conversationId,
1527
1602
  screenshots: sent,
1603
+ // Only when there is something: an older server then sees the body
1604
+ // it always did.
1605
+ ...sentFiles.length > 0 ? { files: sentFiles.map((f) => f.file) } : {},
1528
1606
  context: {
1529
1607
  ...context != null ? context : {},
1530
1608
  imageSources: sent.map((shot) => pageShot && shot === pageShot ? "page" : "attached"),
@@ -1544,7 +1622,7 @@ function AssistSheet({
1544
1622
  setBusy(false);
1545
1623
  }
1546
1624
  if (result.degraded) {
1547
- degradeKeepingEvents(events, result.degraded);
1625
+ degradeKeepingEvents(events, result.degraded, result.retryable === true);
1548
1626
  return;
1549
1627
  }
1550
1628
  if (result.conversationId) {
@@ -2532,7 +2610,7 @@ function AssistSheet({
2532
2610
  // instead of remounting, and a draft is something you can
2533
2611
  // still reply to (#360), not a form that replaced the chat.
2534
2612
  /* @__PURE__ */ jsxs2(Fragment, { children: [
2535
- (screenshots.length > 0 || onAddImages) && /* @__PURE__ */ jsxs2(
2613
+ (screenshots.length > 0 || files.length > 0 || onAddFiles) && /* @__PURE__ */ jsxs2(
2536
2614
  "div",
2537
2615
  {
2538
2616
  style: {
@@ -2636,11 +2714,118 @@ function AssistSheet({
2636
2714
  `${i}-${shot.slice(-16)}`
2637
2715
  );
2638
2716
  }),
2639
- onAddImages && /* @__PURE__ */ jsxs2(
2717
+ files.map((file, i) => {
2718
+ const read = readFiles.has(i);
2719
+ const why = read ? "the assistant reads this file" : isAssistReadableFile(file.name) ? "attached, but more than one message can carry, so the assistant is not reading it" : "attached to the report \u2014 the assistant can't read this kind of file";
2720
+ return /* @__PURE__ */ jsxs2(
2721
+ "div",
2722
+ {
2723
+ "data-gg-assist-file": "",
2724
+ "data-gg-assist-file-read": read ? "" : void 0,
2725
+ title: `${file.name} \u2014 ${why}`,
2726
+ style: {
2727
+ position: "relative",
2728
+ height: "40px",
2729
+ maxWidth: "170px",
2730
+ display: "flex",
2731
+ alignItems: "center",
2732
+ gap: "6px",
2733
+ padding: "0 10px",
2734
+ borderRadius: "8px",
2735
+ border: `1px solid ${read ? t2.accent : t2.inputBorder}`,
2736
+ opacity: read ? 1 : 0.55,
2737
+ boxSizing: "border-box",
2738
+ flexShrink: 0
2739
+ },
2740
+ children: [
2741
+ /* @__PURE__ */ jsx2("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx2(
2742
+ "path",
2743
+ {
2744
+ d: "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z M14 2v6h6",
2745
+ stroke: t2.textMuted,
2746
+ strokeWidth: "1.5",
2747
+ strokeLinecap: "round",
2748
+ strokeLinejoin: "round"
2749
+ }
2750
+ ) }),
2751
+ /* @__PURE__ */ jsx2(
2752
+ "span",
2753
+ {
2754
+ style: {
2755
+ fontSize: "11.5px",
2756
+ color: t2.text,
2757
+ overflow: "hidden",
2758
+ textOverflow: "ellipsis",
2759
+ whiteSpace: "nowrap"
2760
+ },
2761
+ children: file.name
2762
+ }
2763
+ ),
2764
+ onRemoveFile && /* @__PURE__ */ jsx2(
2765
+ "button",
2766
+ {
2767
+ type: "button",
2768
+ "data-gg-assist-remove-file": "",
2769
+ "aria-label": `Remove file ${file.name}`,
2770
+ title: "Remove this file",
2771
+ onClick: () => {
2772
+ logEvent("file_removed", read ? "read" : "not read");
2773
+ onRemoveFile(i);
2774
+ },
2775
+ style: {
2776
+ position: "absolute",
2777
+ top: "-13px",
2778
+ right: "-13px",
2779
+ width: "32px",
2780
+ height: "32px",
2781
+ border: "none",
2782
+ background: "transparent",
2783
+ padding: 0,
2784
+ cursor: "pointer",
2785
+ display: "flex",
2786
+ alignItems: "center",
2787
+ justifyContent: "center",
2788
+ borderRadius: "50%"
2789
+ },
2790
+ children: /* @__PURE__ */ jsx2(
2791
+ "span",
2792
+ {
2793
+ "aria-hidden": "true",
2794
+ style: {
2795
+ width: "20px",
2796
+ height: "20px",
2797
+ boxSizing: "border-box",
2798
+ borderRadius: "50%",
2799
+ border: `2px solid ${t2.bg}`,
2800
+ background: "#ef4444",
2801
+ color: "#fff",
2802
+ display: "flex",
2803
+ alignItems: "center",
2804
+ justifyContent: "center"
2805
+ },
2806
+ children: /* @__PURE__ */ jsx2("svg", { width: "8", height: "8", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ jsx2(
2807
+ "path",
2808
+ {
2809
+ d: "M1 1L9 9M9 1L1 9",
2810
+ stroke: "currentColor",
2811
+ strokeWidth: "1.6",
2812
+ strokeLinecap: "round"
2813
+ }
2814
+ ) })
2815
+ }
2816
+ )
2817
+ }
2818
+ )
2819
+ ]
2820
+ },
2821
+ `file-${i}-${file.name}`
2822
+ );
2823
+ }),
2824
+ onAddFiles && /* @__PURE__ */ jsxs2(
2640
2825
  "label",
2641
2826
  {
2642
2827
  "data-gg-assist-attach": "",
2643
- title: "Attach an image \u2014 or just paste one",
2828
+ title: "Attach an image or a file (html, json, log\u2026) \u2014 or paste one",
2644
2829
  style: {
2645
2830
  width: "40px",
2646
2831
  height: "40px",
@@ -2660,14 +2845,18 @@ function AssistSheet({
2660
2845
  "input",
2661
2846
  {
2662
2847
  type: "file",
2663
- accept: "image/*",
2848
+ accept: ASSIST_ATTACHMENT_ACCEPT,
2664
2849
  multiple: true,
2665
2850
  onChange: (e) => {
2666
2851
  var _a;
2667
- const files = Array.from((_a = e.target.files) != null ? _a : []);
2668
- if (files.length) {
2669
- logEvent("image_added", String(files.length));
2670
- onAddImages(files);
2852
+ const picked = Array.from((_a = e.target.files) != null ? _a : []);
2853
+ if (picked.length) {
2854
+ const images = picked.filter((f) => f.type.startsWith("image/")).length;
2855
+ if (images) logEvent("image_added", String(images));
2856
+ if (picked.length > images) {
2857
+ logEvent("file_added", String(picked.length - images));
2858
+ }
2859
+ onAddFiles(picked);
2671
2860
  }
2672
2861
  e.target.value = "";
2673
2862
  },
@@ -2848,6 +3037,7 @@ function AssistSheet({
2848
3037
  }
2849
3038
  var FONT = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2850
3039
  var FAILED = "Couldn't send it. Try again in a moment.";
3040
+ var MAX_SELF_REPORT_IMAGES = 5;
2851
3041
  function describeSentAlong(hasChat, screenshotCount) {
2852
3042
  const parts = [
2853
3043
  ...hasChat ? ["your AI chat"] : [],
@@ -2871,11 +3061,49 @@ function GlitchgrabProblemPanel({
2871
3061
  const [busy, setBusy] = useState3(false);
2872
3062
  const [sent, setSent] = useState3(false);
2873
3063
  const [error, setError] = useState3(null);
3064
+ const [images, setImages] = useState3(
3065
+ () => screenshots.slice(0, MAX_SELF_REPORT_IMAGES)
3066
+ );
2874
3067
  const inputRef = useRef3(null);
3068
+ const fileRef = useRef3(null);
2875
3069
  useEffect3(() => {
2876
3070
  var _a;
2877
3071
  (_a = inputRef.current) == null ? void 0 : _a.focus();
2878
3072
  }, []);
3073
+ const addImages = useCallback((files) => {
3074
+ const picked = files.filter((file) => file.type.startsWith("image/"));
3075
+ if (!picked.length) return;
3076
+ void Promise.all(picked.map((file) => encodeImageFile(file).catch(() => null))).then(
3077
+ (encoded) => {
3078
+ const added = encoded.filter((url) => !!url);
3079
+ if (added.length) {
3080
+ setImages((prev) => [...prev, ...added].slice(0, MAX_SELF_REPORT_IMAGES));
3081
+ }
3082
+ }
3083
+ );
3084
+ }, []);
3085
+ useEffect3(() => {
3086
+ const onPaste = (e) => {
3087
+ var _a;
3088
+ try {
3089
+ const items = (_a = e.clipboardData) == null ? void 0 : _a.items;
3090
+ if (!items) return;
3091
+ const files = [];
3092
+ for (let i = 0; i < items.length; i++) {
3093
+ if (items[i].kind !== "file") continue;
3094
+ const file = items[i].getAsFile();
3095
+ if (file) files.push(file);
3096
+ }
3097
+ if (!files.length) return;
3098
+ e.preventDefault();
3099
+ e.stopImmediatePropagation();
3100
+ addImages(files);
3101
+ } catch (e2) {
3102
+ }
3103
+ };
3104
+ window.addEventListener("paste", onPaste, true);
3105
+ return () => window.removeEventListener("paste", onPaste, true);
3106
+ }, [addImages]);
2879
3107
  useEffect3(() => {
2880
3108
  const onKey = (e) => {
2881
3109
  if (e.key !== "Escape") return;
@@ -2891,7 +3119,13 @@ function GlitchgrabProblemPanel({
2891
3119
  setBusy(true);
2892
3120
  setError(null);
2893
3121
  try {
2894
- const result = await send({ description, surface, conversationId, reportType, screenshots });
3122
+ const result = await send({
3123
+ description,
3124
+ surface,
3125
+ conversationId,
3126
+ reportType,
3127
+ screenshots: images
3128
+ });
2895
3129
  if (result == null ? void 0 : result.success) setSent(true);
2896
3130
  else setError((result == null ? void 0 : result.message) || FAILED);
2897
3131
  } catch (e) {
@@ -3044,6 +3278,134 @@ function GlitchgrabProblemPanel({
3044
3278
  }
3045
3279
  }
3046
3280
  ),
3281
+ /* @__PURE__ */ jsxs3(
3282
+ "div",
3283
+ {
3284
+ "data-gg-self-report-images": "",
3285
+ style: {
3286
+ display: "flex",
3287
+ flexWrap: "wrap",
3288
+ alignItems: "center",
3289
+ gap: "8px",
3290
+ marginTop: "10px"
3291
+ },
3292
+ children: [
3293
+ images.map((src, i) => /* @__PURE__ */ jsxs3("div", { style: { position: "relative", width: "48px", height: "48px" }, children: [
3294
+ /* @__PURE__ */ jsx3(
3295
+ "img",
3296
+ {
3297
+ src,
3298
+ alt: `Screenshot ${i + 1}`,
3299
+ style: {
3300
+ width: "100%",
3301
+ height: "100%",
3302
+ objectFit: "cover",
3303
+ display: "block",
3304
+ boxSizing: "border-box",
3305
+ borderRadius: "8px",
3306
+ border: `1px solid ${t2.inputBorder}`
3307
+ }
3308
+ }
3309
+ ),
3310
+ /* @__PURE__ */ jsx3(
3311
+ "button",
3312
+ {
3313
+ type: "button",
3314
+ "data-gg-self-report-remove-image": "",
3315
+ "aria-label": `Remove screenshot ${i + 1}`,
3316
+ title: "Don't send this one",
3317
+ onClick: () => setImages((prev) => prev.filter((_, j) => j !== i)),
3318
+ style: {
3319
+ position: "absolute",
3320
+ top: "-13px",
3321
+ right: "-13px",
3322
+ width: "32px",
3323
+ height: "32px",
3324
+ border: "none",
3325
+ background: "transparent",
3326
+ padding: 0,
3327
+ cursor: "pointer",
3328
+ display: "flex",
3329
+ alignItems: "center",
3330
+ justifyContent: "center",
3331
+ borderRadius: "50%"
3332
+ },
3333
+ children: /* @__PURE__ */ jsx3(
3334
+ "span",
3335
+ {
3336
+ "aria-hidden": "true",
3337
+ style: {
3338
+ width: "20px",
3339
+ height: "20px",
3340
+ boxSizing: "border-box",
3341
+ borderRadius: "50%",
3342
+ border: `2px solid ${t2.bg}`,
3343
+ background: "#ef4444",
3344
+ color: "#fff",
3345
+ display: "flex",
3346
+ alignItems: "center",
3347
+ justifyContent: "center"
3348
+ },
3349
+ children: /* @__PURE__ */ jsx3("svg", { width: "8", height: "8", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ jsx3(
3350
+ "path",
3351
+ {
3352
+ d: "M1 1L9 9M9 1L1 9",
3353
+ stroke: "currentColor",
3354
+ strokeWidth: "1.6",
3355
+ strokeLinecap: "round"
3356
+ }
3357
+ ) })
3358
+ }
3359
+ )
3360
+ }
3361
+ )
3362
+ ] }, i)),
3363
+ images.length < MAX_SELF_REPORT_IMAGES && /* @__PURE__ */ jsx3(
3364
+ "button",
3365
+ {
3366
+ type: "button",
3367
+ "data-gg-self-report-attach": "",
3368
+ "aria-label": "Add screenshot",
3369
+ title: "Add a screenshot \u2014 or paste one",
3370
+ onClick: () => {
3371
+ var _a;
3372
+ return (_a = fileRef.current) == null ? void 0 : _a.click();
3373
+ },
3374
+ style: {
3375
+ height: "48px",
3376
+ padding: "0 12px",
3377
+ boxSizing: "border-box",
3378
+ borderRadius: "8px",
3379
+ border: `1px dashed ${t2.inputBorder}`,
3380
+ background: "transparent",
3381
+ color: t2.textMuted,
3382
+ fontSize: "12.5px",
3383
+ fontWeight: 600,
3384
+ fontFamily: "inherit",
3385
+ cursor: "pointer"
3386
+ },
3387
+ children: images.length >= 3 ? "+ Add" : "+ Add screenshot"
3388
+ }
3389
+ ),
3390
+ /* @__PURE__ */ jsx3(
3391
+ "input",
3392
+ {
3393
+ ref: fileRef,
3394
+ type: "file",
3395
+ accept: "image/*",
3396
+ multiple: true,
3397
+ "data-gg-self-report-file": "",
3398
+ onChange: (e) => {
3399
+ var _a;
3400
+ addImages(Array.from((_a = e.target.files) != null ? _a : []));
3401
+ e.target.value = "";
3402
+ },
3403
+ style: { display: "none" }
3404
+ }
3405
+ )
3406
+ ]
3407
+ }
3408
+ ),
3047
3409
  /* @__PURE__ */ jsxs3(
3048
3410
  "p",
3049
3411
  {
@@ -3052,7 +3414,7 @@ function GlitchgrabProblemPanel({
3052
3414
  children: [
3053
3415
  "Also sent, so we can see what happened:",
3054
3416
  " ",
3055
- describeSentAlong(conversationId !== null, screenshots.length),
3417
+ describeSentAlong(conversationId !== null, images.length),
3056
3418
  "."
3057
3419
  ]
3058
3420
  }
@@ -3093,38 +3455,6 @@ function isMac() {
3093
3455
  function getShortcutLabel() {
3094
3456
  return isMac() ? GLITCHGRAB_SHORTCUT_MAC : GLITCHGRAB_SHORTCUT;
3095
3457
  }
3096
- var ATTACHMENT_ACCEPT = [
3097
- "image/*",
3098
- // text — inlined into the issue body
3099
- ".html",
3100
- ".htm",
3101
- ".txt",
3102
- ".md",
3103
- ".json",
3104
- ".xml",
3105
- ".log",
3106
- ".yml",
3107
- ".yaml",
3108
- ".csv",
3109
- ".har",
3110
- ".js",
3111
- ".mjs",
3112
- ".cjs",
3113
- ".ts",
3114
- ".tsx",
3115
- ".jsx",
3116
- ".css",
3117
- ".scss",
3118
- ".py",
3119
- ".sh",
3120
- // binary documents
3121
- ".pdf",
3122
- ".doc",
3123
- ".docx",
3124
- ".xls",
3125
- ".xlsx",
3126
- ".zip"
3127
- ].join(",");
3128
3458
  var MAX_SCALE = 2;
3129
3459
  var FRAME_TIMEOUT_MS = 3e3;
3130
3460
  var GRAB_TIMEOUT_MS = FRAME_TIMEOUT_MS + 2e3;
@@ -3770,7 +4100,7 @@ function ReportDialog({
3770
4100
  const next = current === -1 ? 0 : Math.min(Math.max(current + delta, 0), tiles.length - 1);
3771
4101
  (_c = tiles[next]) == null ? void 0 : _c.focus();
3772
4102
  };
3773
- const isDark = useMemo(() => isOpen ? detectDark() : true, [isOpen]);
4103
+ const isDark = useMemo2(() => isOpen ? detectDark() : true, [isOpen]);
3774
4104
  const t2 = getTheme(isDark);
3775
4105
  const layerTheme = {
3776
4106
  bg: t2.bg,
@@ -6354,8 +6684,10 @@ function ReportDialog({
6354
6684
  theme: layerTheme,
6355
6685
  screenshots,
6356
6686
  pageShot,
6357
- onAddImages: addFiles,
6687
+ onAddFiles: addFiles,
6358
6688
  onRemoveImage: (index) => setScreenshots((prev) => prev.filter((_, i) => i !== index)),
6689
+ files: attachments,
6690
+ onRemoveFile: (index) => setAttachments((prev) => prev.filter((_, i) => i !== index)),
6359
6691
  attachmentCount: screenshots.length + attachments.length,
6360
6692
  context: { ...assistContext != null ? assistContext : {}, reportType },
6361
6693
  reportTypeLabel: getTypeLabel(reportType),
@@ -6397,9 +6729,9 @@ function ReportDialog({
6397
6729
  setSelfReportPrefill(prefill != null ? prefill : "");
6398
6730
  setSelfReportFrom("assist");
6399
6731
  } : void 0,
6400
- onDegrade: (message) => {
6732
+ onDegrade: (message, retryable) => {
6401
6733
  setAssistOpen(false);
6402
- setAssistUsed(true);
6734
+ if (!retryable) setAssistUsed(true);
6403
6735
  setAssistNotice(message);
6404
6736
  },
6405
6737
  onClose: () => {
@@ -6545,7 +6877,7 @@ function getTypeIcon(type, color, size = 24) {
6545
6877
  }
6546
6878
 
6547
6879
  // src/booking-dialog.tsx
6548
- import { useCallback, useEffect as useEffect5, useMemo as useMemo2, useState as useState5 } from "react";
6880
+ import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
6549
6881
  import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
6550
6882
  var t = {
6551
6883
  bg: "#141414",
@@ -6600,11 +6932,11 @@ function BookingDialog({
6600
6932
  const [meetUrl, setMeetUrl] = useState5(null);
6601
6933
  const [busy, setBusy] = useState5(false);
6602
6934
  const [error, setError] = useState5(null);
6603
- const headers = useMemo2(
6935
+ const headers = useMemo3(
6604
6936
  () => ({ Authorization: `Bearer ${token}`, "Content-Type": "application/json" }),
6605
6937
  [token]
6606
6938
  );
6607
- const loadSlots = useCallback(async () => {
6939
+ const loadSlots = useCallback2(async () => {
6608
6940
  var _a2, _b, _c;
6609
6941
  setError(null);
6610
6942
  setSlots(null);
@@ -7111,7 +7443,7 @@ function GlitchgrabProviderInner({
7111
7443
  sessionProp.avatarUrl,
7112
7444
  sessionProp.image
7113
7445
  ].map((v) => v == null ? "" : String(v)).join("\0") : "";
7114
- const session = useMemo3(() => sessionProp, [sessionKey]);
7446
+ const session = useMemo4(() => sessionProp, [sessionKey]);
7115
7447
  setRelease(release2);
7116
7448
  if (appContext2) setContexts(appContext2);
7117
7449
  registerCaptureConfig({
@@ -7292,7 +7624,7 @@ function GlitchgrabProviderInner({
7292
7624
  } catch (e) {
7293
7625
  }
7294
7626
  }, [token, baseUrl, onError, onReportSent, session, ignoreErrors]);
7295
- const report = useCallback2(
7627
+ const report = useCallback3(
7296
7628
  async (type, description, metadata) => {
7297
7629
  var _a;
7298
7630
  try {
@@ -7326,11 +7658,11 @@ function GlitchgrabProviderInner({
7326
7658
  },
7327
7659
  [token, baseUrl, onReportSent, session]
7328
7660
  );
7329
- const reportBug = useCallback2(
7661
+ const reportBug = useCallback3(
7330
7662
  (description, metadata) => report("BUG", description, metadata),
7331
7663
  [report]
7332
7664
  );
7333
- const reportGlitchgrabProblem = useCallback2(
7665
+ const reportGlitchgrabProblem = useCallback3(
7334
7666
  async (params) => {
7335
7667
  var _a, _b;
7336
7668
  try {
@@ -7364,7 +7696,7 @@ function GlitchgrabProviderInner({
7364
7696
  },
7365
7697
  [token, baseUrl, session]
7366
7698
  );
7367
- const feedback = useCallback2(
7699
+ const feedback = useCallback3(
7368
7700
  async (rating, message, metadata) => {
7369
7701
  try {
7370
7702
  const context = captureContext(visitedPagesRef.current);
@@ -7392,26 +7724,26 @@ function GlitchgrabProviderInner({
7392
7724
  },
7393
7725
  [token, baseUrl, session]
7394
7726
  );
7395
- const openFeedbackDialog = useCallback2(() => {
7727
+ const openFeedbackDialog = useCallback3(() => {
7396
7728
  if (typeof window !== "undefined") {
7397
7729
  window.dispatchEvent(
7398
7730
  new CustomEvent("glitchgrab:open-report", { detail: { type: "RATING" } })
7399
7731
  );
7400
7732
  }
7401
7733
  }, []);
7402
- const captureError2 = useCallback2(
7734
+ const captureError2 = useCallback3(
7403
7735
  (error, options) => {
7404
7736
  captureError(error, options);
7405
7737
  },
7406
7738
  []
7407
7739
  );
7408
- const setContext2 = useCallback2((key, value) => {
7740
+ const setContext2 = useCallback3((key, value) => {
7409
7741
  setContext(key, value);
7410
7742
  }, []);
7411
- const setContexts2 = useCallback2((values) => {
7743
+ const setContexts2 = useCallback3((values) => {
7412
7744
  setContexts(values);
7413
7745
  }, []);
7414
- const addBreadcrumb2 = useCallback2(
7746
+ const addBreadcrumb2 = useCallback3(
7415
7747
  (message, data) => {
7416
7748
  addBreadcrumb("custom", message, data);
7417
7749
  },
@@ -7419,11 +7751,11 @@ function GlitchgrabProviderInner({
7419
7751
  );
7420
7752
  const signedUserId = session == null ? void 0 : session.userId;
7421
7753
  const signature = typeof (session == null ? void 0 : session.signature) === "string" ? session.signature : null;
7422
- const identity = useMemo3(
7754
+ const identity = useMemo4(
7423
7755
  () => ({ userId: signedUserId != null ? signedUserId : null, signature }),
7424
7756
  [signedUserId, signature]
7425
7757
  );
7426
- const enhance = useCallback2(
7758
+ const enhance = useCallback3(
7427
7759
  async (text, screenshot) => {
7428
7760
  try {
7429
7761
  const ctx = captureContext(visitedPagesRef.current);
@@ -7439,7 +7771,7 @@ function GlitchgrabProviderInner({
7439
7771
  },
7440
7772
  [token, baseUrl, identity]
7441
7773
  );
7442
- const assist = useCallback2(
7774
+ const assist = useCallback3(
7443
7775
  async (params) => {
7444
7776
  var _a;
7445
7777
  const ctx = captureContext(visitedPagesRef.current);
@@ -7466,11 +7798,11 @@ function GlitchgrabProviderInner({
7466
7798
  },
7467
7799
  [token, baseUrl, session]
7468
7800
  );
7469
- const findSimilar = useCallback2(
7801
+ const findSimilar = useCallback3(
7470
7802
  (description) => findSimilarIssues(description, token, baseUrl, identity),
7471
7803
  [token, baseUrl, identity]
7472
7804
  );
7473
- const transcribe = useCallback2(
7805
+ const transcribe = useCallback3(
7474
7806
  async (blob) => {
7475
7807
  try {
7476
7808
  return await transcribeAudio(blob, token, baseUrl, identity);
@@ -7480,7 +7812,7 @@ function GlitchgrabProviderInner({
7480
7812
  },
7481
7813
  [token, baseUrl, identity]
7482
7814
  );
7483
- const openReportDialog = useCallback2((options) => {
7815
+ const openReportDialog = useCallback3((options) => {
7484
7816
  if (typeof window !== "undefined") {
7485
7817
  window.dispatchEvent(new CustomEvent("glitchgrab:open-report", { detail: options }));
7486
7818
  }
@@ -7499,7 +7831,7 @@ function GlitchgrabProviderInner({
7499
7831
  const [signedUsers, setSignedUsers] = useState6("OFF");
7500
7832
  const aiBlockedUnsigned = signedUsers === "ENFORCE" && !signature;
7501
7833
  const [bookingOpen, setBookingOpen] = useState6(false);
7502
- const openBookingDialog = useCallback2(() => setBookingOpen(true), []);
7834
+ const openBookingDialog = useCallback3(() => setBookingOpen(true), []);
7503
7835
  useEffect6(() => {
7504
7836
  let cancelled = false;
7505
7837
  fetch(`${baseUrl}/api/v1/sdk/project`, {
@@ -7543,7 +7875,7 @@ function GlitchgrabProviderInner({
7543
7875
  } catch (e) {
7544
7876
  }
7545
7877
  }, [openReportDialog]);
7546
- const reporter = useMemo3(() => {
7878
+ const reporter = useMemo4(() => {
7547
7879
  var _a;
7548
7880
  if (!session) return null;
7549
7881
  const avatar = (_a = session.avatarUrl) != null ? _a : session.image;
@@ -7842,7 +8174,7 @@ function FeedbackButton({
7842
8174
  }
7843
8175
 
7844
8176
  // src/use-reports.ts
7845
- import { useState as useState8, useEffect as useEffect7, useCallback as useCallback3 } from "react";
8177
+ import { useState as useState8, useEffect as useEffect7, useCallback as useCallback4 } from "react";
7846
8178
  var BASE_URL = "https://glitchgrab.dev";
7847
8179
  function useGlitchgrabReports({
7848
8180
  token,
@@ -7853,7 +8185,7 @@ function useGlitchgrabReports({
7853
8185
  const [isLoading, setIsLoading] = useState8(true);
7854
8186
  const [isFetching, setIsFetching] = useState8(false);
7855
8187
  const [error, setError] = useState8(null);
7856
- const fetchReports = useCallback3(async () => {
8188
+ const fetchReports = useCallback4(async () => {
7857
8189
  var _a, _b;
7858
8190
  if (!token || !userId) {
7859
8191
  setIsLoading(false);
@@ -7918,7 +8250,7 @@ function useGlitchgrabActions({
7918
8250
  }) {
7919
8251
  const [isPending, setIsPending] = useState8(false);
7920
8252
  const [error, setError] = useState8(null);
7921
- const run = useCallback3(
8253
+ const run = useCallback4(
7922
8254
  async (reportId, body) => {
7923
8255
  setIsPending(true);
7924
8256
  setError(null);
@@ -7948,7 +8280,7 @@ function useGlitchgrabActions({
7948
8280
  }
7949
8281
 
7950
8282
  // src/use-feedback.ts
7951
- import { useState as useState9, useEffect as useEffect8, useCallback as useCallback4 } from "react";
8283
+ import { useState as useState9, useEffect as useEffect8, useCallback as useCallback5 } from "react";
7952
8284
  var DEFAULT_BASE_URL3 = "https://glitchgrab.dev";
7953
8285
  function buildUrl({
7954
8286
  approvedOnly,
@@ -7978,7 +8310,7 @@ function useGlitchgrabFeedback(query) {
7978
8310
  const [isLoading, setIsLoading] = useState9(true);
7979
8311
  const [isFetching, setIsFetching] = useState9(false);
7980
8312
  const [error, setError] = useState9(null);
7981
- const load = useCallback4(async () => {
8313
+ const load = useCallback5(async () => {
7982
8314
  if (!token) {
7983
8315
  setIsLoading(false);
7984
8316
  return;