glitchgrab 1.44.0 → 1.46.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) {
@@ -1727,6 +1805,7 @@ function AssistSheet({
1727
1805
  /* @__PURE__ */ jsxs2(
1728
1806
  "div",
1729
1807
  {
1808
+ "data-gg-sheet-header": "",
1730
1809
  style: {
1731
1810
  flexShrink: 0,
1732
1811
  padding: "14px 16px",
@@ -1738,7 +1817,7 @@ function AssistSheet({
1738
1817
  minWidth: 0
1739
1818
  },
1740
1819
  children: [
1741
- /* @__PURE__ */ jsxs2("div", { style: { minWidth: 0, overflow: "hidden" }, children: [
1820
+ /* @__PURE__ */ jsxs2("div", { style: { minWidth: 0 }, children: [
1742
1821
  /* @__PURE__ */ jsxs2(
1743
1822
  "div",
1744
1823
  {
@@ -2532,7 +2611,7 @@ function AssistSheet({
2532
2611
  // instead of remounting, and a draft is something you can
2533
2612
  // still reply to (#360), not a form that replaced the chat.
2534
2613
  /* @__PURE__ */ jsxs2(Fragment, { children: [
2535
- (screenshots.length > 0 || onAddImages) && /* @__PURE__ */ jsxs2(
2614
+ (screenshots.length > 0 || files.length > 0 || onAddFiles) && /* @__PURE__ */ jsxs2(
2536
2615
  "div",
2537
2616
  {
2538
2617
  style: {
@@ -2636,11 +2715,118 @@ function AssistSheet({
2636
2715
  `${i}-${shot.slice(-16)}`
2637
2716
  );
2638
2717
  }),
2639
- onAddImages && /* @__PURE__ */ jsxs2(
2718
+ files.map((file, i) => {
2719
+ const read = readFiles.has(i);
2720
+ 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";
2721
+ return /* @__PURE__ */ jsxs2(
2722
+ "div",
2723
+ {
2724
+ "data-gg-assist-file": "",
2725
+ "data-gg-assist-file-read": read ? "" : void 0,
2726
+ title: `${file.name} \u2014 ${why}`,
2727
+ style: {
2728
+ position: "relative",
2729
+ height: "40px",
2730
+ maxWidth: "170px",
2731
+ display: "flex",
2732
+ alignItems: "center",
2733
+ gap: "6px",
2734
+ padding: "0 10px",
2735
+ borderRadius: "8px",
2736
+ border: `1px solid ${read ? t2.accent : t2.inputBorder}`,
2737
+ opacity: read ? 1 : 0.55,
2738
+ boxSizing: "border-box",
2739
+ flexShrink: 0
2740
+ },
2741
+ children: [
2742
+ /* @__PURE__ */ jsx2("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx2(
2743
+ "path",
2744
+ {
2745
+ d: "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z M14 2v6h6",
2746
+ stroke: t2.textMuted,
2747
+ strokeWidth: "1.5",
2748
+ strokeLinecap: "round",
2749
+ strokeLinejoin: "round"
2750
+ }
2751
+ ) }),
2752
+ /* @__PURE__ */ jsx2(
2753
+ "span",
2754
+ {
2755
+ style: {
2756
+ fontSize: "11.5px",
2757
+ color: t2.text,
2758
+ overflow: "hidden",
2759
+ textOverflow: "ellipsis",
2760
+ whiteSpace: "nowrap"
2761
+ },
2762
+ children: file.name
2763
+ }
2764
+ ),
2765
+ onRemoveFile && /* @__PURE__ */ jsx2(
2766
+ "button",
2767
+ {
2768
+ type: "button",
2769
+ "data-gg-assist-remove-file": "",
2770
+ "aria-label": `Remove file ${file.name}`,
2771
+ title: "Remove this file",
2772
+ onClick: () => {
2773
+ logEvent("file_removed", read ? "read" : "not read");
2774
+ onRemoveFile(i);
2775
+ },
2776
+ style: {
2777
+ position: "absolute",
2778
+ top: "-13px",
2779
+ right: "-13px",
2780
+ width: "32px",
2781
+ height: "32px",
2782
+ border: "none",
2783
+ background: "transparent",
2784
+ padding: 0,
2785
+ cursor: "pointer",
2786
+ display: "flex",
2787
+ alignItems: "center",
2788
+ justifyContent: "center",
2789
+ borderRadius: "50%"
2790
+ },
2791
+ children: /* @__PURE__ */ jsx2(
2792
+ "span",
2793
+ {
2794
+ "aria-hidden": "true",
2795
+ style: {
2796
+ width: "20px",
2797
+ height: "20px",
2798
+ boxSizing: "border-box",
2799
+ borderRadius: "50%",
2800
+ border: `2px solid ${t2.bg}`,
2801
+ background: "#ef4444",
2802
+ color: "#fff",
2803
+ display: "flex",
2804
+ alignItems: "center",
2805
+ justifyContent: "center"
2806
+ },
2807
+ children: /* @__PURE__ */ jsx2("svg", { width: "8", height: "8", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ jsx2(
2808
+ "path",
2809
+ {
2810
+ d: "M1 1L9 9M9 1L1 9",
2811
+ stroke: "currentColor",
2812
+ strokeWidth: "1.6",
2813
+ strokeLinecap: "round"
2814
+ }
2815
+ ) })
2816
+ }
2817
+ )
2818
+ }
2819
+ )
2820
+ ]
2821
+ },
2822
+ `file-${i}-${file.name}`
2823
+ );
2824
+ }),
2825
+ onAddFiles && /* @__PURE__ */ jsxs2(
2640
2826
  "label",
2641
2827
  {
2642
2828
  "data-gg-assist-attach": "",
2643
- title: "Attach an image \u2014 or just paste one",
2829
+ title: "Attach an image or a file (html, json, log\u2026) \u2014 or paste one",
2644
2830
  style: {
2645
2831
  width: "40px",
2646
2832
  height: "40px",
@@ -2660,14 +2846,18 @@ function AssistSheet({
2660
2846
  "input",
2661
2847
  {
2662
2848
  type: "file",
2663
- accept: "image/*",
2849
+ accept: ASSIST_ATTACHMENT_ACCEPT,
2664
2850
  multiple: true,
2665
2851
  onChange: (e) => {
2666
2852
  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);
2853
+ const picked = Array.from((_a = e.target.files) != null ? _a : []);
2854
+ if (picked.length) {
2855
+ const images = picked.filter((f) => f.type.startsWith("image/")).length;
2856
+ if (images) logEvent("image_added", String(images));
2857
+ if (picked.length > images) {
2858
+ logEvent("file_added", String(picked.length - images));
2859
+ }
2860
+ onAddFiles(picked);
2671
2861
  }
2672
2862
  e.target.value = "";
2673
2863
  },
@@ -2848,6 +3038,7 @@ function AssistSheet({
2848
3038
  }
2849
3039
  var FONT = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2850
3040
  var FAILED = "Couldn't send it. Try again in a moment.";
3041
+ var MAX_SELF_REPORT_IMAGES = 5;
2851
3042
  function describeSentAlong(hasChat, screenshotCount) {
2852
3043
  const parts = [
2853
3044
  ...hasChat ? ["your AI chat"] : [],
@@ -2871,11 +3062,49 @@ function GlitchgrabProblemPanel({
2871
3062
  const [busy, setBusy] = useState3(false);
2872
3063
  const [sent, setSent] = useState3(false);
2873
3064
  const [error, setError] = useState3(null);
3065
+ const [images, setImages] = useState3(
3066
+ () => screenshots.slice(0, MAX_SELF_REPORT_IMAGES)
3067
+ );
2874
3068
  const inputRef = useRef3(null);
3069
+ const fileRef = useRef3(null);
2875
3070
  useEffect3(() => {
2876
3071
  var _a;
2877
3072
  (_a = inputRef.current) == null ? void 0 : _a.focus();
2878
3073
  }, []);
3074
+ const addImages = useCallback((files) => {
3075
+ const picked = files.filter((file) => file.type.startsWith("image/"));
3076
+ if (!picked.length) return;
3077
+ void Promise.all(picked.map((file) => encodeImageFile(file).catch(() => null))).then(
3078
+ (encoded) => {
3079
+ const added = encoded.filter((url) => !!url);
3080
+ if (added.length) {
3081
+ setImages((prev) => [...prev, ...added].slice(0, MAX_SELF_REPORT_IMAGES));
3082
+ }
3083
+ }
3084
+ );
3085
+ }, []);
3086
+ useEffect3(() => {
3087
+ const onPaste = (e) => {
3088
+ var _a;
3089
+ try {
3090
+ const items = (_a = e.clipboardData) == null ? void 0 : _a.items;
3091
+ if (!items) return;
3092
+ const files = [];
3093
+ for (let i = 0; i < items.length; i++) {
3094
+ if (items[i].kind !== "file") continue;
3095
+ const file = items[i].getAsFile();
3096
+ if (file) files.push(file);
3097
+ }
3098
+ if (!files.length) return;
3099
+ e.preventDefault();
3100
+ e.stopImmediatePropagation();
3101
+ addImages(files);
3102
+ } catch (e2) {
3103
+ }
3104
+ };
3105
+ window.addEventListener("paste", onPaste, true);
3106
+ return () => window.removeEventListener("paste", onPaste, true);
3107
+ }, [addImages]);
2879
3108
  useEffect3(() => {
2880
3109
  const onKey = (e) => {
2881
3110
  if (e.key !== "Escape") return;
@@ -2891,7 +3120,13 @@ function GlitchgrabProblemPanel({
2891
3120
  setBusy(true);
2892
3121
  setError(null);
2893
3122
  try {
2894
- const result = await send({ description, surface, conversationId, reportType, screenshots });
3123
+ const result = await send({
3124
+ description,
3125
+ surface,
3126
+ conversationId,
3127
+ reportType,
3128
+ screenshots: images
3129
+ });
2895
3130
  if (result == null ? void 0 : result.success) setSent(true);
2896
3131
  else setError((result == null ? void 0 : result.message) || FAILED);
2897
3132
  } catch (e) {
@@ -3044,6 +3279,134 @@ function GlitchgrabProblemPanel({
3044
3279
  }
3045
3280
  }
3046
3281
  ),
3282
+ /* @__PURE__ */ jsxs3(
3283
+ "div",
3284
+ {
3285
+ "data-gg-self-report-images": "",
3286
+ style: {
3287
+ display: "flex",
3288
+ flexWrap: "wrap",
3289
+ alignItems: "center",
3290
+ gap: "8px",
3291
+ marginTop: "10px"
3292
+ },
3293
+ children: [
3294
+ images.map((src, i) => /* @__PURE__ */ jsxs3("div", { style: { position: "relative", width: "48px", height: "48px" }, children: [
3295
+ /* @__PURE__ */ jsx3(
3296
+ "img",
3297
+ {
3298
+ src,
3299
+ alt: `Screenshot ${i + 1}`,
3300
+ style: {
3301
+ width: "100%",
3302
+ height: "100%",
3303
+ objectFit: "cover",
3304
+ display: "block",
3305
+ boxSizing: "border-box",
3306
+ borderRadius: "8px",
3307
+ border: `1px solid ${t2.inputBorder}`
3308
+ }
3309
+ }
3310
+ ),
3311
+ /* @__PURE__ */ jsx3(
3312
+ "button",
3313
+ {
3314
+ type: "button",
3315
+ "data-gg-self-report-remove-image": "",
3316
+ "aria-label": `Remove screenshot ${i + 1}`,
3317
+ title: "Don't send this one",
3318
+ onClick: () => setImages((prev) => prev.filter((_, j) => j !== i)),
3319
+ style: {
3320
+ position: "absolute",
3321
+ top: "-13px",
3322
+ right: "-13px",
3323
+ width: "32px",
3324
+ height: "32px",
3325
+ border: "none",
3326
+ background: "transparent",
3327
+ padding: 0,
3328
+ cursor: "pointer",
3329
+ display: "flex",
3330
+ alignItems: "center",
3331
+ justifyContent: "center",
3332
+ borderRadius: "50%"
3333
+ },
3334
+ children: /* @__PURE__ */ jsx3(
3335
+ "span",
3336
+ {
3337
+ "aria-hidden": "true",
3338
+ style: {
3339
+ width: "20px",
3340
+ height: "20px",
3341
+ boxSizing: "border-box",
3342
+ borderRadius: "50%",
3343
+ border: `2px solid ${t2.bg}`,
3344
+ background: "#ef4444",
3345
+ color: "#fff",
3346
+ display: "flex",
3347
+ alignItems: "center",
3348
+ justifyContent: "center"
3349
+ },
3350
+ children: /* @__PURE__ */ jsx3("svg", { width: "8", height: "8", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ jsx3(
3351
+ "path",
3352
+ {
3353
+ d: "M1 1L9 9M9 1L1 9",
3354
+ stroke: "currentColor",
3355
+ strokeWidth: "1.6",
3356
+ strokeLinecap: "round"
3357
+ }
3358
+ ) })
3359
+ }
3360
+ )
3361
+ }
3362
+ )
3363
+ ] }, i)),
3364
+ images.length < MAX_SELF_REPORT_IMAGES && /* @__PURE__ */ jsx3(
3365
+ "button",
3366
+ {
3367
+ type: "button",
3368
+ "data-gg-self-report-attach": "",
3369
+ "aria-label": "Add screenshot",
3370
+ title: "Add a screenshot \u2014 or paste one",
3371
+ onClick: () => {
3372
+ var _a;
3373
+ return (_a = fileRef.current) == null ? void 0 : _a.click();
3374
+ },
3375
+ style: {
3376
+ height: "48px",
3377
+ padding: "0 12px",
3378
+ boxSizing: "border-box",
3379
+ borderRadius: "8px",
3380
+ border: `1px dashed ${t2.inputBorder}`,
3381
+ background: "transparent",
3382
+ color: t2.textMuted,
3383
+ fontSize: "12.5px",
3384
+ fontWeight: 600,
3385
+ fontFamily: "inherit",
3386
+ cursor: "pointer"
3387
+ },
3388
+ children: images.length >= 3 ? "+ Add" : "+ Add screenshot"
3389
+ }
3390
+ ),
3391
+ /* @__PURE__ */ jsx3(
3392
+ "input",
3393
+ {
3394
+ ref: fileRef,
3395
+ type: "file",
3396
+ accept: "image/*",
3397
+ multiple: true,
3398
+ "data-gg-self-report-file": "",
3399
+ onChange: (e) => {
3400
+ var _a;
3401
+ addImages(Array.from((_a = e.target.files) != null ? _a : []));
3402
+ e.target.value = "";
3403
+ },
3404
+ style: { display: "none" }
3405
+ }
3406
+ )
3407
+ ]
3408
+ }
3409
+ ),
3047
3410
  /* @__PURE__ */ jsxs3(
3048
3411
  "p",
3049
3412
  {
@@ -3052,7 +3415,7 @@ function GlitchgrabProblemPanel({
3052
3415
  children: [
3053
3416
  "Also sent, so we can see what happened:",
3054
3417
  " ",
3055
- describeSentAlong(conversationId !== null, screenshots.length),
3418
+ describeSentAlong(conversationId !== null, images.length),
3056
3419
  "."
3057
3420
  ]
3058
3421
  }
@@ -3093,38 +3456,6 @@ function isMac() {
3093
3456
  function getShortcutLabel() {
3094
3457
  return isMac() ? GLITCHGRAB_SHORTCUT_MAC : GLITCHGRAB_SHORTCUT;
3095
3458
  }
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
3459
  var MAX_SCALE = 2;
3129
3460
  var FRAME_TIMEOUT_MS = 3e3;
3130
3461
  var GRAB_TIMEOUT_MS = FRAME_TIMEOUT_MS + 2e3;
@@ -3637,6 +3968,7 @@ function ReportDialog({
3637
3968
  const [description, setDescription] = useState4("");
3638
3969
  const [isSubmitting, setIsSubmitting] = useState4(false);
3639
3970
  const [submitted, setSubmitted] = useState4(false);
3971
+ const [queuedMessage, setQueuedMessage] = useState4(null);
3640
3972
  const [screenshots, setScreenshots] = useState4([]);
3641
3973
  const [pageShot, setPageShot] = useState4(null);
3642
3974
  const [shotPending, setShotPending] = useState4(false);
@@ -3770,7 +4102,7 @@ function ReportDialog({
3770
4102
  const next = current === -1 ? 0 : Math.min(Math.max(current + delta, 0), tiles.length - 1);
3771
4103
  (_c = tiles[next]) == null ? void 0 : _c.focus();
3772
4104
  };
3773
- const isDark = useMemo(() => isOpen ? detectDark() : true, [isOpen]);
4105
+ const isDark = useMemo2(() => isOpen ? detectDark() : true, [isOpen]);
3774
4106
  const t2 = getTheme(isDark);
3775
4107
  const layerTheme = {
3776
4108
  bg: t2.bg,
@@ -4242,7 +4574,7 @@ function ReportDialog({
4242
4574
  }
4243
4575
  };
4244
4576
  const handleSubmit = async (opts) => {
4245
- var _a2;
4577
+ var _a2, _b;
4246
4578
  try {
4247
4579
  if (isSubmitting) return;
4248
4580
  if (isRating) {
@@ -4328,6 +4660,8 @@ function ReportDialog({
4328
4660
  );
4329
4661
  if (result) {
4330
4662
  setAddedToIssue(duplicateOf && result.issueNumber === duplicateOf ? duplicateOf : null);
4663
+ const queued = result.status === "QUEUED" && !!result.message;
4664
+ setQueuedMessage(queued ? (_b = result.message) != null ? _b : null : null);
4331
4665
  setSubmitted(true);
4332
4666
  setDescription("");
4333
4667
  setScreenshots([]);
@@ -4338,8 +4672,9 @@ function ReportDialog({
4338
4672
  setPickedSimilar(null);
4339
4673
  setTimeout(() => {
4340
4674
  setSubmitted(false);
4675
+ setQueuedMessage(null);
4341
4676
  handleClose();
4342
- }, 2e3);
4677
+ }, queued ? 6e3 : 2e3);
4343
4678
  }
4344
4679
  setIsSubmitting(false);
4345
4680
  setAttachingTo(null);
@@ -4649,7 +4984,18 @@ function ReportDialog({
4649
4984
  fontSize: "14px",
4650
4985
  fontWeight: 500
4651
4986
  },
4652
- children: addedToIssue ? `Added to #${addedToIssue}. Thank you!` : `${getTypeLabel(reportType)} sent. Thank you!`
4987
+ children: queuedMessage ? /* @__PURE__ */ jsx4(
4988
+ "span",
4989
+ {
4990
+ style: {
4991
+ // amber-500 reads on the dark panel, amber-700 on the light one
4992
+ color: isDark ? "#f59e0b" : "#b45309",
4993
+ fontWeight: 400,
4994
+ lineHeight: 1.5
4995
+ },
4996
+ children: queuedMessage
4997
+ }
4998
+ ) : addedToIssue ? `Added to #${addedToIssue}. Thank you!` : `${getTypeLabel(reportType)} sent. Thank you!`
4653
4999
  }
4654
5000
  ) : /* @__PURE__ */ jsxs4(Fragment3, { children: [
4655
5001
  step === 1 && headerSlot ? /* @__PURE__ */ jsx4("div", { style: { marginBottom: "14px" }, children: headerSlot }) : null,
@@ -6354,8 +6700,10 @@ function ReportDialog({
6354
6700
  theme: layerTheme,
6355
6701
  screenshots,
6356
6702
  pageShot,
6357
- onAddImages: addFiles,
6703
+ onAddFiles: addFiles,
6358
6704
  onRemoveImage: (index) => setScreenshots((prev) => prev.filter((_, i) => i !== index)),
6705
+ files: attachments,
6706
+ onRemoveFile: (index) => setAttachments((prev) => prev.filter((_, i) => i !== index)),
6359
6707
  attachmentCount: screenshots.length + attachments.length,
6360
6708
  context: { ...assistContext != null ? assistContext : {}, reportType },
6361
6709
  reportTypeLabel: getTypeLabel(reportType),
@@ -6397,9 +6745,9 @@ function ReportDialog({
6397
6745
  setSelfReportPrefill(prefill != null ? prefill : "");
6398
6746
  setSelfReportFrom("assist");
6399
6747
  } : void 0,
6400
- onDegrade: (message) => {
6748
+ onDegrade: (message, retryable) => {
6401
6749
  setAssistOpen(false);
6402
- setAssistUsed(true);
6750
+ if (!retryable) setAssistUsed(true);
6403
6751
  setAssistNotice(message);
6404
6752
  },
6405
6753
  onClose: () => {
@@ -6545,7 +6893,7 @@ function getTypeIcon(type, color, size = 24) {
6545
6893
  }
6546
6894
 
6547
6895
  // src/booking-dialog.tsx
6548
- import { useCallback, useEffect as useEffect5, useMemo as useMemo2, useState as useState5 } from "react";
6896
+ import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
6549
6897
  import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
6550
6898
  var t = {
6551
6899
  bg: "#141414",
@@ -6600,11 +6948,11 @@ function BookingDialog({
6600
6948
  const [meetUrl, setMeetUrl] = useState5(null);
6601
6949
  const [busy, setBusy] = useState5(false);
6602
6950
  const [error, setError] = useState5(null);
6603
- const headers = useMemo2(
6951
+ const headers = useMemo3(
6604
6952
  () => ({ Authorization: `Bearer ${token}`, "Content-Type": "application/json" }),
6605
6953
  [token]
6606
6954
  );
6607
- const loadSlots = useCallback(async () => {
6955
+ const loadSlots = useCallback2(async () => {
6608
6956
  var _a2, _b, _c;
6609
6957
  setError(null);
6610
6958
  setSlots(null);
@@ -7111,7 +7459,7 @@ function GlitchgrabProviderInner({
7111
7459
  sessionProp.avatarUrl,
7112
7460
  sessionProp.image
7113
7461
  ].map((v) => v == null ? "" : String(v)).join("\0") : "";
7114
- const session = useMemo3(() => sessionProp, [sessionKey]);
7462
+ const session = useMemo4(() => sessionProp, [sessionKey]);
7115
7463
  setRelease(release2);
7116
7464
  if (appContext2) setContexts(appContext2);
7117
7465
  registerCaptureConfig({
@@ -7292,7 +7640,7 @@ function GlitchgrabProviderInner({
7292
7640
  } catch (e) {
7293
7641
  }
7294
7642
  }, [token, baseUrl, onError, onReportSent, session, ignoreErrors]);
7295
- const report = useCallback2(
7643
+ const report = useCallback3(
7296
7644
  async (type, description, metadata) => {
7297
7645
  var _a;
7298
7646
  try {
@@ -7326,11 +7674,11 @@ function GlitchgrabProviderInner({
7326
7674
  },
7327
7675
  [token, baseUrl, onReportSent, session]
7328
7676
  );
7329
- const reportBug = useCallback2(
7677
+ const reportBug = useCallback3(
7330
7678
  (description, metadata) => report("BUG", description, metadata),
7331
7679
  [report]
7332
7680
  );
7333
- const reportGlitchgrabProblem = useCallback2(
7681
+ const reportGlitchgrabProblem = useCallback3(
7334
7682
  async (params) => {
7335
7683
  var _a, _b;
7336
7684
  try {
@@ -7364,7 +7712,7 @@ function GlitchgrabProviderInner({
7364
7712
  },
7365
7713
  [token, baseUrl, session]
7366
7714
  );
7367
- const feedback = useCallback2(
7715
+ const feedback = useCallback3(
7368
7716
  async (rating, message, metadata) => {
7369
7717
  try {
7370
7718
  const context = captureContext(visitedPagesRef.current);
@@ -7392,26 +7740,26 @@ function GlitchgrabProviderInner({
7392
7740
  },
7393
7741
  [token, baseUrl, session]
7394
7742
  );
7395
- const openFeedbackDialog = useCallback2(() => {
7743
+ const openFeedbackDialog = useCallback3(() => {
7396
7744
  if (typeof window !== "undefined") {
7397
7745
  window.dispatchEvent(
7398
7746
  new CustomEvent("glitchgrab:open-report", { detail: { type: "RATING" } })
7399
7747
  );
7400
7748
  }
7401
7749
  }, []);
7402
- const captureError2 = useCallback2(
7750
+ const captureError2 = useCallback3(
7403
7751
  (error, options) => {
7404
7752
  captureError(error, options);
7405
7753
  },
7406
7754
  []
7407
7755
  );
7408
- const setContext2 = useCallback2((key, value) => {
7756
+ const setContext2 = useCallback3((key, value) => {
7409
7757
  setContext(key, value);
7410
7758
  }, []);
7411
- const setContexts2 = useCallback2((values) => {
7759
+ const setContexts2 = useCallback3((values) => {
7412
7760
  setContexts(values);
7413
7761
  }, []);
7414
- const addBreadcrumb2 = useCallback2(
7762
+ const addBreadcrumb2 = useCallback3(
7415
7763
  (message, data) => {
7416
7764
  addBreadcrumb("custom", message, data);
7417
7765
  },
@@ -7419,11 +7767,11 @@ function GlitchgrabProviderInner({
7419
7767
  );
7420
7768
  const signedUserId = session == null ? void 0 : session.userId;
7421
7769
  const signature = typeof (session == null ? void 0 : session.signature) === "string" ? session.signature : null;
7422
- const identity = useMemo3(
7770
+ const identity = useMemo4(
7423
7771
  () => ({ userId: signedUserId != null ? signedUserId : null, signature }),
7424
7772
  [signedUserId, signature]
7425
7773
  );
7426
- const enhance = useCallback2(
7774
+ const enhance = useCallback3(
7427
7775
  async (text, screenshot) => {
7428
7776
  try {
7429
7777
  const ctx = captureContext(visitedPagesRef.current);
@@ -7439,7 +7787,7 @@ function GlitchgrabProviderInner({
7439
7787
  },
7440
7788
  [token, baseUrl, identity]
7441
7789
  );
7442
- const assist = useCallback2(
7790
+ const assist = useCallback3(
7443
7791
  async (params) => {
7444
7792
  var _a;
7445
7793
  const ctx = captureContext(visitedPagesRef.current);
@@ -7466,11 +7814,11 @@ function GlitchgrabProviderInner({
7466
7814
  },
7467
7815
  [token, baseUrl, session]
7468
7816
  );
7469
- const findSimilar = useCallback2(
7817
+ const findSimilar = useCallback3(
7470
7818
  (description) => findSimilarIssues(description, token, baseUrl, identity),
7471
7819
  [token, baseUrl, identity]
7472
7820
  );
7473
- const transcribe = useCallback2(
7821
+ const transcribe = useCallback3(
7474
7822
  async (blob) => {
7475
7823
  try {
7476
7824
  return await transcribeAudio(blob, token, baseUrl, identity);
@@ -7480,7 +7828,7 @@ function GlitchgrabProviderInner({
7480
7828
  },
7481
7829
  [token, baseUrl, identity]
7482
7830
  );
7483
- const openReportDialog = useCallback2((options) => {
7831
+ const openReportDialog = useCallback3((options) => {
7484
7832
  if (typeof window !== "undefined") {
7485
7833
  window.dispatchEvent(new CustomEvent("glitchgrab:open-report", { detail: options }));
7486
7834
  }
@@ -7499,7 +7847,7 @@ function GlitchgrabProviderInner({
7499
7847
  const [signedUsers, setSignedUsers] = useState6("OFF");
7500
7848
  const aiBlockedUnsigned = signedUsers === "ENFORCE" && !signature;
7501
7849
  const [bookingOpen, setBookingOpen] = useState6(false);
7502
- const openBookingDialog = useCallback2(() => setBookingOpen(true), []);
7850
+ const openBookingDialog = useCallback3(() => setBookingOpen(true), []);
7503
7851
  useEffect6(() => {
7504
7852
  let cancelled = false;
7505
7853
  fetch(`${baseUrl}/api/v1/sdk/project`, {
@@ -7543,7 +7891,7 @@ function GlitchgrabProviderInner({
7543
7891
  } catch (e) {
7544
7892
  }
7545
7893
  }, [openReportDialog]);
7546
- const reporter = useMemo3(() => {
7894
+ const reporter = useMemo4(() => {
7547
7895
  var _a;
7548
7896
  if (!session) return null;
7549
7897
  const avatar = (_a = session.avatarUrl) != null ? _a : session.image;
@@ -7842,7 +8190,7 @@ function FeedbackButton({
7842
8190
  }
7843
8191
 
7844
8192
  // src/use-reports.ts
7845
- import { useState as useState8, useEffect as useEffect7, useCallback as useCallback3 } from "react";
8193
+ import { useState as useState8, useEffect as useEffect7, useCallback as useCallback4 } from "react";
7846
8194
  var BASE_URL = "https://glitchgrab.dev";
7847
8195
  function useGlitchgrabReports({
7848
8196
  token,
@@ -7853,7 +8201,7 @@ function useGlitchgrabReports({
7853
8201
  const [isLoading, setIsLoading] = useState8(true);
7854
8202
  const [isFetching, setIsFetching] = useState8(false);
7855
8203
  const [error, setError] = useState8(null);
7856
- const fetchReports = useCallback3(async () => {
8204
+ const fetchReports = useCallback4(async () => {
7857
8205
  var _a, _b;
7858
8206
  if (!token || !userId) {
7859
8207
  setIsLoading(false);
@@ -7918,7 +8266,7 @@ function useGlitchgrabActions({
7918
8266
  }) {
7919
8267
  const [isPending, setIsPending] = useState8(false);
7920
8268
  const [error, setError] = useState8(null);
7921
- const run = useCallback3(
8269
+ const run = useCallback4(
7922
8270
  async (reportId, body) => {
7923
8271
  setIsPending(true);
7924
8272
  setError(null);
@@ -7948,7 +8296,7 @@ function useGlitchgrabActions({
7948
8296
  }
7949
8297
 
7950
8298
  // src/use-feedback.ts
7951
- import { useState as useState9, useEffect as useEffect8, useCallback as useCallback4 } from "react";
8299
+ import { useState as useState9, useEffect as useEffect8, useCallback as useCallback5 } from "react";
7952
8300
  var DEFAULT_BASE_URL3 = "https://glitchgrab.dev";
7953
8301
  function buildUrl({
7954
8302
  approvedOnly,
@@ -7978,7 +8326,7 @@ function useGlitchgrabFeedback(query) {
7978
8326
  const [isLoading, setIsLoading] = useState9(true);
7979
8327
  const [isFetching, setIsFetching] = useState9(false);
7980
8328
  const [error, setError] = useState9(null);
7981
- const load = useCallback4(async () => {
8329
+ const load = useCallback5(async () => {
7982
8330
  if (!token) {
7983
8331
  setIsLoading(false);
7984
8332
  return;