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.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { f as GlitchgrabProviderProps, U as UseGlitchgrabReturn, R as ReportButtonProps, F as FeedbackButtonProps, g as GlitchgrabSession, C as CaptureErrorOptions, e as GlitchgrabFeedback, a as BreadcrumbType, B as Breadcrumb, b as CapturedContext, D as DeviceInfo, c as FeedbackPayload, d as FeedbackResult, h as ReportPayload, i as ReportResult } from './types-Lldp5noh.mjs';
3
- export { G as GlitchgrabConfig, j as ReportSeverity, k as ReportType, l as RuntimeInfo } from './types-Lldp5noh.mjs';
2
+ import { f as GlitchgrabProviderProps, U as UseGlitchgrabReturn, R as ReportButtonProps, F as FeedbackButtonProps, g as GlitchgrabSession, C as CaptureErrorOptions, e as GlitchgrabFeedback, a as BreadcrumbType, B as Breadcrumb, b as CapturedContext, D as DeviceInfo, c as FeedbackPayload, d as FeedbackResult, h as ReportPayload, i as ReportResult } from './types-DcDF1mXc.mjs';
3
+ export { G as GlitchgrabConfig, j as ReportSeverity, k as ReportType, l as RuntimeInfo } from './types-DcDF1mXc.mjs';
4
4
  import * as React from 'react';
5
5
  import { ReactNode } from 'react';
6
6
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { f as GlitchgrabProviderProps, U as UseGlitchgrabReturn, R as ReportButtonProps, F as FeedbackButtonProps, g as GlitchgrabSession, C as CaptureErrorOptions, e as GlitchgrabFeedback, a as BreadcrumbType, B as Breadcrumb, b as CapturedContext, D as DeviceInfo, c as FeedbackPayload, d as FeedbackResult, h as ReportPayload, i as ReportResult } from './types-Lldp5noh.js';
3
- export { G as GlitchgrabConfig, j as ReportSeverity, k as ReportType, l as RuntimeInfo } from './types-Lldp5noh.js';
2
+ import { f as GlitchgrabProviderProps, U as UseGlitchgrabReturn, R as ReportButtonProps, F as FeedbackButtonProps, g as GlitchgrabSession, C as CaptureErrorOptions, e as GlitchgrabFeedback, a as BreadcrumbType, B as Breadcrumb, b as CapturedContext, D as DeviceInfo, c as FeedbackPayload, d as FeedbackResult, h as ReportPayload, i as ReportResult } from './types-DcDF1mXc.js';
3
+ export { G as GlitchgrabConfig, j as ReportSeverity, k as ReportType, l as RuntimeInfo } from './types-DcDF1mXc.js';
4
4
  import * as React from 'react';
5
5
  import { ReactNode } from 'react';
6
6
 
package/dist/index.js CHANGED
@@ -770,7 +770,9 @@ async function assistTurn(params, token, baseUrl) {
770
770
  conversationId: null,
771
771
  question: null,
772
772
  report: null,
773
- degraded: (_a = envelope == null ? void 0 : envelope.error) != null ? _a : offline.degraded
773
+ degraded: (_a = envelope == null ? void 0 : envelope.error) != null ? _a : offline.degraded,
774
+ // An hourly limit, not a cap: the dialog keeps the assistant button.
775
+ retryable: (envelope == null ? void 0 : envelope.retryable) === true
774
776
  };
775
777
  }
776
778
  return {
@@ -1367,6 +1369,59 @@ var SEVERITY_LABELS = {
1367
1369
  };
1368
1370
  var MAX_ASSIST_IMAGES = 3;
1369
1371
  var MAX_ASSIST_IMAGE_CHARS = 35e5;
1372
+ var MAX_ASSIST_FILES = 3;
1373
+ var MAX_ASSIST_FILE_CHARS = 3e5;
1374
+ var MAX_ASSIST_FILE_CHARS_EACH = 2e5;
1375
+ var TEXT_ATTACHMENT_EXTENSIONS = [
1376
+ ".html",
1377
+ ".htm",
1378
+ ".txt",
1379
+ ".md",
1380
+ ".json",
1381
+ ".xml",
1382
+ ".log",
1383
+ ".yml",
1384
+ ".yaml",
1385
+ ".csv",
1386
+ ".har",
1387
+ ".js",
1388
+ ".mjs",
1389
+ ".cjs",
1390
+ ".ts",
1391
+ ".tsx",
1392
+ ".jsx",
1393
+ ".css",
1394
+ ".scss",
1395
+ ".py",
1396
+ ".sh"
1397
+ ];
1398
+ var BINARY_ATTACHMENT_EXTENSIONS = [".pdf", ".doc", ".docx", ".xls", ".xlsx", ".zip"];
1399
+ var ATTACHMENT_ACCEPT = [
1400
+ "image/*",
1401
+ ...TEXT_ATTACHMENT_EXTENSIONS,
1402
+ ...BINARY_ATTACHMENT_EXTENSIONS
1403
+ ].join(",");
1404
+ var ASSIST_ATTACHMENT_ACCEPT = ["image/*", ...TEXT_ATTACHMENT_EXTENSIONS].join(",");
1405
+ function isAssistReadableFile(name) {
1406
+ const lower = name.toLowerCase();
1407
+ return TEXT_ATTACHMENT_EXTENSIONS.some((ext) => lower.endsWith(ext));
1408
+ }
1409
+ function decodeTextDataUrl(dataUrl) {
1410
+ try {
1411
+ if (!dataUrl.startsWith("data:")) return null;
1412
+ const comma = dataUrl.indexOf(",");
1413
+ if (comma < 0) return null;
1414
+ const meta = dataUrl.slice(5, comma);
1415
+ const payload = dataUrl.slice(comma + 1);
1416
+ if (!meta.endsWith(";base64")) return decodeURIComponent(payload);
1417
+ const binary = atob(payload);
1418
+ const bytes = new Uint8Array(binary.length);
1419
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
1420
+ return new TextDecoder("utf-8").decode(bytes);
1421
+ } catch (e) {
1422
+ return null;
1423
+ }
1424
+ }
1370
1425
  function getTypeLabel(type) {
1371
1426
  switch (type) {
1372
1427
  case "RATING":
@@ -1398,6 +1453,22 @@ function pickAssistImages(all) {
1398
1453
  }
1399
1454
  return picked;
1400
1455
  }
1456
+ var NO_FILES = [];
1457
+ function pickAssistFiles(files) {
1458
+ const picked = [];
1459
+ let total = 0;
1460
+ for (let i = files.length - 1; i >= 0 && picked.length < MAX_ASSIST_FILES; i--) {
1461
+ const attached = files[i];
1462
+ if (!isAssistReadableFile(attached.name)) continue;
1463
+ const text = decodeTextDataUrl(attached.dataUrl);
1464
+ if (!text || !text.trim()) continue;
1465
+ const content = text.slice(0, MAX_ASSIST_FILE_CHARS_EACH);
1466
+ if (total + content.length > MAX_ASSIST_FILE_CHARS) continue;
1467
+ picked.unshift({ index: i, file: { name: attached.name, content } });
1468
+ total += content.length;
1469
+ }
1470
+ return picked;
1471
+ }
1401
1472
  var STARTERS = [
1402
1473
  "Something on this page is broken",
1403
1474
  "It did the wrong thing",
@@ -1474,7 +1545,9 @@ function AssistSheet({
1474
1545
  theme: t2,
1475
1546
  screenshots,
1476
1547
  pageShot = null,
1477
- onAddImages,
1548
+ onAddFiles,
1549
+ files = NO_FILES,
1550
+ onRemoveFile,
1478
1551
  onRemoveImage,
1479
1552
  attachmentCount,
1480
1553
  context,
@@ -1545,6 +1618,8 @@ function AssistSheet({
1545
1618
  }
1546
1619
  }
1547
1620
  const readShots = new Set(pickAssistImages(screenshots));
1621
+ const sentFiles = (0, import_react3.useMemo)(() => pickAssistFiles(files), [files]);
1622
+ const readFiles = new Set(sentFiles.map((f) => f.index));
1548
1623
  function leave(type, detail) {
1549
1624
  logEvent(type, detail);
1550
1625
  flushEvents();
@@ -1558,10 +1633,10 @@ function AssistSheet({
1558
1633
  var _a;
1559
1634
  if (phase === "draft") (_a = draftRef.current) == null ? void 0 : _a.focus();
1560
1635
  }, [phase]);
1561
- function degradeKeepingEvents(events, message) {
1636
+ function degradeKeepingEvents(events, message, retryable) {
1562
1637
  if (events) eventsRef.current = [...events, ...eventsRef.current];
1563
1638
  flushEvents();
1564
- onDegrade(message);
1639
+ onDegrade(message, retryable);
1565
1640
  }
1566
1641
  async function runTurn(history2) {
1567
1642
  var _a, _b, _c, _d;
@@ -1579,6 +1654,9 @@ function AssistSheet({
1579
1654
  ),
1580
1655
  conversationId,
1581
1656
  screenshots: sent,
1657
+ // Only when there is something: an older server then sees the body
1658
+ // it always did.
1659
+ ...sentFiles.length > 0 ? { files: sentFiles.map((f) => f.file) } : {},
1582
1660
  context: {
1583
1661
  ...context != null ? context : {},
1584
1662
  imageSources: sent.map((shot) => pageShot && shot === pageShot ? "page" : "attached"),
@@ -1598,7 +1676,7 @@ function AssistSheet({
1598
1676
  setBusy(false);
1599
1677
  }
1600
1678
  if (result.degraded) {
1601
- degradeKeepingEvents(events, result.degraded);
1679
+ degradeKeepingEvents(events, result.degraded, result.retryable === true);
1602
1680
  return;
1603
1681
  }
1604
1682
  if (result.conversationId) {
@@ -1781,6 +1859,7 @@ function AssistSheet({
1781
1859
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1782
1860
  "div",
1783
1861
  {
1862
+ "data-gg-sheet-header": "",
1784
1863
  style: {
1785
1864
  flexShrink: 0,
1786
1865
  padding: "14px 16px",
@@ -1792,7 +1871,7 @@ function AssistSheet({
1792
1871
  minWidth: 0
1793
1872
  },
1794
1873
  children: [
1795
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { minWidth: 0, overflow: "hidden" }, children: [
1874
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { minWidth: 0 }, children: [
1796
1875
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1797
1876
  "div",
1798
1877
  {
@@ -2586,7 +2665,7 @@ function AssistSheet({
2586
2665
  // instead of remounting, and a draft is something you can
2587
2666
  // still reply to (#360), not a form that replaced the chat.
2588
2667
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
2589
- (screenshots.length > 0 || onAddImages) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2668
+ (screenshots.length > 0 || files.length > 0 || onAddFiles) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2590
2669
  "div",
2591
2670
  {
2592
2671
  style: {
@@ -2690,11 +2769,118 @@ function AssistSheet({
2690
2769
  `${i}-${shot.slice(-16)}`
2691
2770
  );
2692
2771
  }),
2693
- onAddImages && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2772
+ files.map((file, i) => {
2773
+ const read = readFiles.has(i);
2774
+ 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";
2775
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2776
+ "div",
2777
+ {
2778
+ "data-gg-assist-file": "",
2779
+ "data-gg-assist-file-read": read ? "" : void 0,
2780
+ title: `${file.name} \u2014 ${why}`,
2781
+ style: {
2782
+ position: "relative",
2783
+ height: "40px",
2784
+ maxWidth: "170px",
2785
+ display: "flex",
2786
+ alignItems: "center",
2787
+ gap: "6px",
2788
+ padding: "0 10px",
2789
+ borderRadius: "8px",
2790
+ border: `1px solid ${read ? t2.accent : t2.inputBorder}`,
2791
+ opacity: read ? 1 : 0.55,
2792
+ boxSizing: "border-box",
2793
+ flexShrink: 0
2794
+ },
2795
+ children: [
2796
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", style: { flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2797
+ "path",
2798
+ {
2799
+ d: "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z M14 2v6h6",
2800
+ stroke: t2.textMuted,
2801
+ strokeWidth: "1.5",
2802
+ strokeLinecap: "round",
2803
+ strokeLinejoin: "round"
2804
+ }
2805
+ ) }),
2806
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2807
+ "span",
2808
+ {
2809
+ style: {
2810
+ fontSize: "11.5px",
2811
+ color: t2.text,
2812
+ overflow: "hidden",
2813
+ textOverflow: "ellipsis",
2814
+ whiteSpace: "nowrap"
2815
+ },
2816
+ children: file.name
2817
+ }
2818
+ ),
2819
+ onRemoveFile && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2820
+ "button",
2821
+ {
2822
+ type: "button",
2823
+ "data-gg-assist-remove-file": "",
2824
+ "aria-label": `Remove file ${file.name}`,
2825
+ title: "Remove this file",
2826
+ onClick: () => {
2827
+ logEvent("file_removed", read ? "read" : "not read");
2828
+ onRemoveFile(i);
2829
+ },
2830
+ style: {
2831
+ position: "absolute",
2832
+ top: "-13px",
2833
+ right: "-13px",
2834
+ width: "32px",
2835
+ height: "32px",
2836
+ border: "none",
2837
+ background: "transparent",
2838
+ padding: 0,
2839
+ cursor: "pointer",
2840
+ display: "flex",
2841
+ alignItems: "center",
2842
+ justifyContent: "center",
2843
+ borderRadius: "50%"
2844
+ },
2845
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2846
+ "span",
2847
+ {
2848
+ "aria-hidden": "true",
2849
+ style: {
2850
+ width: "20px",
2851
+ height: "20px",
2852
+ boxSizing: "border-box",
2853
+ borderRadius: "50%",
2854
+ border: `2px solid ${t2.bg}`,
2855
+ background: "#ef4444",
2856
+ color: "#fff",
2857
+ display: "flex",
2858
+ alignItems: "center",
2859
+ justifyContent: "center"
2860
+ },
2861
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "8", height: "8", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2862
+ "path",
2863
+ {
2864
+ d: "M1 1L9 9M9 1L1 9",
2865
+ stroke: "currentColor",
2866
+ strokeWidth: "1.6",
2867
+ strokeLinecap: "round"
2868
+ }
2869
+ ) })
2870
+ }
2871
+ )
2872
+ }
2873
+ )
2874
+ ]
2875
+ },
2876
+ `file-${i}-${file.name}`
2877
+ );
2878
+ }),
2879
+ onAddFiles && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2694
2880
  "label",
2695
2881
  {
2696
2882
  "data-gg-assist-attach": "",
2697
- title: "Attach an image \u2014 or just paste one",
2883
+ title: "Attach an image or a file (html, json, log\u2026) \u2014 or paste one",
2698
2884
  style: {
2699
2885
  width: "40px",
2700
2886
  height: "40px",
@@ -2714,14 +2900,18 @@ function AssistSheet({
2714
2900
  "input",
2715
2901
  {
2716
2902
  type: "file",
2717
- accept: "image/*",
2903
+ accept: ASSIST_ATTACHMENT_ACCEPT,
2718
2904
  multiple: true,
2719
2905
  onChange: (e) => {
2720
2906
  var _a;
2721
- const files = Array.from((_a = e.target.files) != null ? _a : []);
2722
- if (files.length) {
2723
- logEvent("image_added", String(files.length));
2724
- onAddImages(files);
2907
+ const picked = Array.from((_a = e.target.files) != null ? _a : []);
2908
+ if (picked.length) {
2909
+ const images = picked.filter((f) => f.type.startsWith("image/")).length;
2910
+ if (images) logEvent("image_added", String(images));
2911
+ if (picked.length > images) {
2912
+ logEvent("file_added", String(picked.length - images));
2913
+ }
2914
+ onAddFiles(picked);
2725
2915
  }
2726
2916
  e.target.value = "";
2727
2917
  },
@@ -2902,6 +3092,7 @@ function AssistSheet({
2902
3092
  }
2903
3093
  var FONT = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2904
3094
  var FAILED = "Couldn't send it. Try again in a moment.";
3095
+ var MAX_SELF_REPORT_IMAGES = 5;
2905
3096
  function describeSentAlong(hasChat, screenshotCount) {
2906
3097
  const parts = [
2907
3098
  ...hasChat ? ["your AI chat"] : [],
@@ -2925,11 +3116,49 @@ function GlitchgrabProblemPanel({
2925
3116
  const [busy, setBusy] = (0, import_react4.useState)(false);
2926
3117
  const [sent, setSent] = (0, import_react4.useState)(false);
2927
3118
  const [error, setError] = (0, import_react4.useState)(null);
3119
+ const [images, setImages] = (0, import_react4.useState)(
3120
+ () => screenshots.slice(0, MAX_SELF_REPORT_IMAGES)
3121
+ );
2928
3122
  const inputRef = (0, import_react4.useRef)(null);
3123
+ const fileRef = (0, import_react4.useRef)(null);
2929
3124
  (0, import_react4.useEffect)(() => {
2930
3125
  var _a;
2931
3126
  (_a = inputRef.current) == null ? void 0 : _a.focus();
2932
3127
  }, []);
3128
+ const addImages = (0, import_react4.useCallback)((files) => {
3129
+ const picked = files.filter((file) => file.type.startsWith("image/"));
3130
+ if (!picked.length) return;
3131
+ void Promise.all(picked.map((file) => encodeImageFile(file).catch(() => null))).then(
3132
+ (encoded) => {
3133
+ const added = encoded.filter((url) => !!url);
3134
+ if (added.length) {
3135
+ setImages((prev) => [...prev, ...added].slice(0, MAX_SELF_REPORT_IMAGES));
3136
+ }
3137
+ }
3138
+ );
3139
+ }, []);
3140
+ (0, import_react4.useEffect)(() => {
3141
+ const onPaste = (e) => {
3142
+ var _a;
3143
+ try {
3144
+ const items = (_a = e.clipboardData) == null ? void 0 : _a.items;
3145
+ if (!items) return;
3146
+ const files = [];
3147
+ for (let i = 0; i < items.length; i++) {
3148
+ if (items[i].kind !== "file") continue;
3149
+ const file = items[i].getAsFile();
3150
+ if (file) files.push(file);
3151
+ }
3152
+ if (!files.length) return;
3153
+ e.preventDefault();
3154
+ e.stopImmediatePropagation();
3155
+ addImages(files);
3156
+ } catch (e2) {
3157
+ }
3158
+ };
3159
+ window.addEventListener("paste", onPaste, true);
3160
+ return () => window.removeEventListener("paste", onPaste, true);
3161
+ }, [addImages]);
2933
3162
  (0, import_react4.useEffect)(() => {
2934
3163
  const onKey = (e) => {
2935
3164
  if (e.key !== "Escape") return;
@@ -2945,7 +3174,13 @@ function GlitchgrabProblemPanel({
2945
3174
  setBusy(true);
2946
3175
  setError(null);
2947
3176
  try {
2948
- const result = await send({ description, surface, conversationId, reportType, screenshots });
3177
+ const result = await send({
3178
+ description,
3179
+ surface,
3180
+ conversationId,
3181
+ reportType,
3182
+ screenshots: images
3183
+ });
2949
3184
  if (result == null ? void 0 : result.success) setSent(true);
2950
3185
  else setError((result == null ? void 0 : result.message) || FAILED);
2951
3186
  } catch (e) {
@@ -3098,6 +3333,134 @@ function GlitchgrabProblemPanel({
3098
3333
  }
3099
3334
  }
3100
3335
  ),
3336
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3337
+ "div",
3338
+ {
3339
+ "data-gg-self-report-images": "",
3340
+ style: {
3341
+ display: "flex",
3342
+ flexWrap: "wrap",
3343
+ alignItems: "center",
3344
+ gap: "8px",
3345
+ marginTop: "10px"
3346
+ },
3347
+ children: [
3348
+ images.map((src, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { position: "relative", width: "48px", height: "48px" }, children: [
3349
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3350
+ "img",
3351
+ {
3352
+ src,
3353
+ alt: `Screenshot ${i + 1}`,
3354
+ style: {
3355
+ width: "100%",
3356
+ height: "100%",
3357
+ objectFit: "cover",
3358
+ display: "block",
3359
+ boxSizing: "border-box",
3360
+ borderRadius: "8px",
3361
+ border: `1px solid ${t2.inputBorder}`
3362
+ }
3363
+ }
3364
+ ),
3365
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3366
+ "button",
3367
+ {
3368
+ type: "button",
3369
+ "data-gg-self-report-remove-image": "",
3370
+ "aria-label": `Remove screenshot ${i + 1}`,
3371
+ title: "Don't send this one",
3372
+ onClick: () => setImages((prev) => prev.filter((_, j) => j !== i)),
3373
+ style: {
3374
+ position: "absolute",
3375
+ top: "-13px",
3376
+ right: "-13px",
3377
+ width: "32px",
3378
+ height: "32px",
3379
+ border: "none",
3380
+ background: "transparent",
3381
+ padding: 0,
3382
+ cursor: "pointer",
3383
+ display: "flex",
3384
+ alignItems: "center",
3385
+ justifyContent: "center",
3386
+ borderRadius: "50%"
3387
+ },
3388
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3389
+ "span",
3390
+ {
3391
+ "aria-hidden": "true",
3392
+ style: {
3393
+ width: "20px",
3394
+ height: "20px",
3395
+ boxSizing: "border-box",
3396
+ borderRadius: "50%",
3397
+ border: `2px solid ${t2.bg}`,
3398
+ background: "#ef4444",
3399
+ color: "#fff",
3400
+ display: "flex",
3401
+ alignItems: "center",
3402
+ justifyContent: "center"
3403
+ },
3404
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: "8", height: "8", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3405
+ "path",
3406
+ {
3407
+ d: "M1 1L9 9M9 1L1 9",
3408
+ stroke: "currentColor",
3409
+ strokeWidth: "1.6",
3410
+ strokeLinecap: "round"
3411
+ }
3412
+ ) })
3413
+ }
3414
+ )
3415
+ }
3416
+ )
3417
+ ] }, i)),
3418
+ images.length < MAX_SELF_REPORT_IMAGES && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3419
+ "button",
3420
+ {
3421
+ type: "button",
3422
+ "data-gg-self-report-attach": "",
3423
+ "aria-label": "Add screenshot",
3424
+ title: "Add a screenshot \u2014 or paste one",
3425
+ onClick: () => {
3426
+ var _a;
3427
+ return (_a = fileRef.current) == null ? void 0 : _a.click();
3428
+ },
3429
+ style: {
3430
+ height: "48px",
3431
+ padding: "0 12px",
3432
+ boxSizing: "border-box",
3433
+ borderRadius: "8px",
3434
+ border: `1px dashed ${t2.inputBorder}`,
3435
+ background: "transparent",
3436
+ color: t2.textMuted,
3437
+ fontSize: "12.5px",
3438
+ fontWeight: 600,
3439
+ fontFamily: "inherit",
3440
+ cursor: "pointer"
3441
+ },
3442
+ children: images.length >= 3 ? "+ Add" : "+ Add screenshot"
3443
+ }
3444
+ ),
3445
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3446
+ "input",
3447
+ {
3448
+ ref: fileRef,
3449
+ type: "file",
3450
+ accept: "image/*",
3451
+ multiple: true,
3452
+ "data-gg-self-report-file": "",
3453
+ onChange: (e) => {
3454
+ var _a;
3455
+ addImages(Array.from((_a = e.target.files) != null ? _a : []));
3456
+ e.target.value = "";
3457
+ },
3458
+ style: { display: "none" }
3459
+ }
3460
+ )
3461
+ ]
3462
+ }
3463
+ ),
3101
3464
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3102
3465
  "p",
3103
3466
  {
@@ -3106,7 +3469,7 @@ function GlitchgrabProblemPanel({
3106
3469
  children: [
3107
3470
  "Also sent, so we can see what happened:",
3108
3471
  " ",
3109
- describeSentAlong(conversationId !== null, screenshots.length),
3472
+ describeSentAlong(conversationId !== null, images.length),
3110
3473
  "."
3111
3474
  ]
3112
3475
  }
@@ -3147,38 +3510,6 @@ function isMac() {
3147
3510
  function getShortcutLabel() {
3148
3511
  return isMac() ? GLITCHGRAB_SHORTCUT_MAC : GLITCHGRAB_SHORTCUT;
3149
3512
  }
3150
- var ATTACHMENT_ACCEPT = [
3151
- "image/*",
3152
- // text — inlined into the issue body
3153
- ".html",
3154
- ".htm",
3155
- ".txt",
3156
- ".md",
3157
- ".json",
3158
- ".xml",
3159
- ".log",
3160
- ".yml",
3161
- ".yaml",
3162
- ".csv",
3163
- ".har",
3164
- ".js",
3165
- ".mjs",
3166
- ".cjs",
3167
- ".ts",
3168
- ".tsx",
3169
- ".jsx",
3170
- ".css",
3171
- ".scss",
3172
- ".py",
3173
- ".sh",
3174
- // binary documents
3175
- ".pdf",
3176
- ".doc",
3177
- ".docx",
3178
- ".xls",
3179
- ".xlsx",
3180
- ".zip"
3181
- ].join(",");
3182
3513
  var MAX_SCALE = 2;
3183
3514
  var FRAME_TIMEOUT_MS = 3e3;
3184
3515
  var GRAB_TIMEOUT_MS = FRAME_TIMEOUT_MS + 2e3;
@@ -3691,6 +4022,7 @@ function ReportDialog({
3691
4022
  const [description, setDescription] = (0, import_react.useState)("");
3692
4023
  const [isSubmitting, setIsSubmitting] = (0, import_react.useState)(false);
3693
4024
  const [submitted, setSubmitted] = (0, import_react.useState)(false);
4025
+ const [queuedMessage, setQueuedMessage] = (0, import_react.useState)(null);
3694
4026
  const [screenshots, setScreenshots] = (0, import_react.useState)([]);
3695
4027
  const [pageShot, setPageShot] = (0, import_react.useState)(null);
3696
4028
  const [shotPending, setShotPending] = (0, import_react.useState)(false);
@@ -4296,7 +4628,7 @@ function ReportDialog({
4296
4628
  }
4297
4629
  };
4298
4630
  const handleSubmit = async (opts) => {
4299
- var _a2;
4631
+ var _a2, _b;
4300
4632
  try {
4301
4633
  if (isSubmitting) return;
4302
4634
  if (isRating) {
@@ -4382,6 +4714,8 @@ function ReportDialog({
4382
4714
  );
4383
4715
  if (result) {
4384
4716
  setAddedToIssue(duplicateOf && result.issueNumber === duplicateOf ? duplicateOf : null);
4717
+ const queued = result.status === "QUEUED" && !!result.message;
4718
+ setQueuedMessage(queued ? (_b = result.message) != null ? _b : null : null);
4385
4719
  setSubmitted(true);
4386
4720
  setDescription("");
4387
4721
  setScreenshots([]);
@@ -4392,8 +4726,9 @@ function ReportDialog({
4392
4726
  setPickedSimilar(null);
4393
4727
  setTimeout(() => {
4394
4728
  setSubmitted(false);
4729
+ setQueuedMessage(null);
4395
4730
  handleClose();
4396
- }, 2e3);
4731
+ }, queued ? 6e3 : 2e3);
4397
4732
  }
4398
4733
  setIsSubmitting(false);
4399
4734
  setAttachingTo(null);
@@ -4703,7 +5038,18 @@ function ReportDialog({
4703
5038
  fontSize: "14px",
4704
5039
  fontWeight: 500
4705
5040
  },
4706
- children: addedToIssue ? `Added to #${addedToIssue}. Thank you!` : `${getTypeLabel(reportType)} sent. Thank you!`
5041
+ children: queuedMessage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
5042
+ "span",
5043
+ {
5044
+ style: {
5045
+ // amber-500 reads on the dark panel, amber-700 on the light one
5046
+ color: isDark ? "#f59e0b" : "#b45309",
5047
+ fontWeight: 400,
5048
+ lineHeight: 1.5
5049
+ },
5050
+ children: queuedMessage
5051
+ }
5052
+ ) : addedToIssue ? `Added to #${addedToIssue}. Thank you!` : `${getTypeLabel(reportType)} sent. Thank you!`
4707
5053
  }
4708
5054
  ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
4709
5055
  step === 1 && headerSlot ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { marginBottom: "14px" }, children: headerSlot }) : null,
@@ -6408,8 +6754,10 @@ function ReportDialog({
6408
6754
  theme: layerTheme,
6409
6755
  screenshots,
6410
6756
  pageShot,
6411
- onAddImages: addFiles,
6757
+ onAddFiles: addFiles,
6412
6758
  onRemoveImage: (index) => setScreenshots((prev) => prev.filter((_, i) => i !== index)),
6759
+ files: attachments,
6760
+ onRemoveFile: (index) => setAttachments((prev) => prev.filter((_, i) => i !== index)),
6413
6761
  attachmentCount: screenshots.length + attachments.length,
6414
6762
  context: { ...assistContext != null ? assistContext : {}, reportType },
6415
6763
  reportTypeLabel: getTypeLabel(reportType),
@@ -6451,9 +6799,9 @@ function ReportDialog({
6451
6799
  setSelfReportPrefill(prefill != null ? prefill : "");
6452
6800
  setSelfReportFrom("assist");
6453
6801
  } : void 0,
6454
- onDegrade: (message) => {
6802
+ onDegrade: (message, retryable) => {
6455
6803
  setAssistOpen(false);
6456
- setAssistUsed(true);
6804
+ if (!retryable) setAssistUsed(true);
6457
6805
  setAssistNotice(message);
6458
6806
  },
6459
6807
  onClose: () => {