glitchgrab 1.41.0 → 1.42.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
@@ -849,6 +849,7 @@ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-run
849
849
  var MAX_SCREENSHOT_DATA_URL_LENGTH = 18e5;
850
850
  var QUALITY_LADDER = [0.92, 0.85, 0.75];
851
851
  var DOWNSCALE_FACTOR = 0.7;
852
+ var MAX_ROUNDS = 4;
852
853
  function downscale(canvas, factor) {
853
854
  const next = document.createElement("canvas");
854
855
  next.width = Math.max(1, Math.round(canvas.width * factor));
@@ -862,18 +863,55 @@ function downscale(canvas, factor) {
862
863
  }
863
864
  function encodeScreenshot(canvas, maxLength = MAX_SCREENSHOT_DATA_URL_LENGTH) {
864
865
  let current = canvas;
865
- for (let round = 0; round < 2; round++) {
866
- let encoded = "";
866
+ let encoded = "";
867
+ for (let round = 0; round < MAX_ROUNDS; round++) {
867
868
  for (const quality of QUALITY_LADDER) {
868
869
  encoded = current.toDataURL("image/jpeg", quality);
869
870
  if (encoded.length <= maxLength) return encoded;
870
871
  }
871
- if (round === 1) return encoded;
872
+ if (round === MAX_ROUNDS - 1) break;
872
873
  const smaller = downscale(current, DOWNSCALE_FACTOR);
873
- if (!smaller) return encoded;
874
+ if (!smaller) break;
874
875
  current = smaller;
875
876
  }
876
- return current.toDataURL("image/jpeg", QUALITY_LADDER[0]);
877
+ return encoded;
878
+ }
879
+ function readAsDataUrl(file) {
880
+ return new Promise((resolve, reject) => {
881
+ const reader = new FileReader();
882
+ reader.onload = () => resolve(reader.result);
883
+ reader.onerror = () => reject(reader.error);
884
+ reader.readAsDataURL(file);
885
+ });
886
+ }
887
+ async function reencodeImageFile(file, maxLength) {
888
+ var _a;
889
+ if (typeof createImageBitmap !== "function") return null;
890
+ const bitmap = await createImageBitmap(file);
891
+ try {
892
+ const canvas = document.createElement("canvas");
893
+ canvas.width = bitmap.width;
894
+ canvas.height = bitmap.height;
895
+ const ctx = canvas.getContext("2d");
896
+ if (!ctx) return null;
897
+ ctx.fillStyle = "#ffffff";
898
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
899
+ ctx.drawImage(bitmap, 0, 0);
900
+ return encodeScreenshot(canvas, maxLength);
901
+ } finally {
902
+ (_a = bitmap.close) == null ? void 0 : _a.call(bitmap);
903
+ }
904
+ }
905
+ async function encodeImageFile(file, maxLength = MAX_SCREENSHOT_DATA_URL_LENGTH) {
906
+ const rawLength = Math.ceil(file.size / 3) * 4 + 64;
907
+ if (rawLength > maxLength) {
908
+ try {
909
+ const shrunk = await reencodeImageFile(file, maxLength);
910
+ if (shrunk && shrunk.length < rawLength) return shrunk;
911
+ } catch (e) {
912
+ }
913
+ }
914
+ return readAsDataUrl(file);
877
915
  }
878
916
  var PALETTE = ["#ef4444", "#f59e0b", "#22c55e", "#3b82f6", "#a855f7", "#ffffff"];
879
917
  function drawStroke(ctx, s) {
@@ -1164,6 +1202,7 @@ var SEVERITY_LABELS = {
1164
1202
  critical: "Critical"
1165
1203
  };
1166
1204
  var MAX_ASSIST_IMAGES = 3;
1205
+ var MAX_ASSIST_IMAGE_CHARS = 35e5;
1167
1206
  function getTypeLabel(type) {
1168
1207
  switch (type) {
1169
1208
  case "RATING":
@@ -1184,6 +1223,17 @@ function getTypeLabel(type) {
1184
1223
  return "Other";
1185
1224
  }
1186
1225
  }
1226
+ function pickAssistImages(all) {
1227
+ const picked = [];
1228
+ let total = 0;
1229
+ for (let i = all.length - 1; i >= 0 && picked.length < MAX_ASSIST_IMAGES; i--) {
1230
+ const shot = all[i];
1231
+ if (total + shot.length > MAX_ASSIST_IMAGE_CHARS) continue;
1232
+ picked.unshift(shot);
1233
+ total += shot.length;
1234
+ }
1235
+ return picked;
1236
+ }
1187
1237
  var STARTERS = [
1188
1238
  "Something on this page is broken",
1189
1239
  "It did the wrong thing",
@@ -1261,6 +1311,7 @@ function AssistSheet({
1261
1311
  screenshots,
1262
1312
  pageShot = null,
1263
1313
  onAddImages,
1314
+ onRemoveImage,
1264
1315
  attachmentCount,
1265
1316
  context,
1266
1317
  reportTypeLabel,
@@ -1302,7 +1353,6 @@ function AssistSheet({
1302
1353
  !typePicked ? "type" : currentTile === "RATING" ? "rating" : "chat"
1303
1354
  );
1304
1355
  const scrollRef = useRef2(null);
1305
- const inputRef = useRef2(null);
1306
1356
  const draftRef = useRef2(null);
1307
1357
  const seededRef = useRef2(false);
1308
1358
  const eventsRef = useRef2([]);
@@ -1328,6 +1378,7 @@ function AssistSheet({
1328
1378
  } catch (e) {
1329
1379
  }
1330
1380
  }
1381
+ const readShots = new Set(pickAssistImages(screenshots));
1331
1382
  function leave(type, detail) {
1332
1383
  logEvent(type, detail);
1333
1384
  flushEvents();
@@ -1347,12 +1398,12 @@ function AssistSheet({
1347
1398
  onDegrade(message);
1348
1399
  }
1349
1400
  async function runTurn(history2) {
1350
- var _a, _b, _c, _d, _e;
1401
+ var _a, _b, _c, _d;
1351
1402
  setBusy(true);
1352
1403
  let result;
1353
1404
  const events = takeEvents();
1354
1405
  try {
1355
- const sent = screenshots.slice(-MAX_ASSIST_IMAGES);
1406
+ const sent = pickAssistImages(screenshots);
1356
1407
  result = await assist({
1357
1408
  // `kind` only on drafts, so an older server sees the exact shape it
1358
1409
  // always did — it just learns the draft text now instead of a
@@ -1362,7 +1413,6 @@ function AssistSheet({
1362
1413
  ),
1363
1414
  conversationId,
1364
1415
  screenshots: sent,
1365
- screenshot: (_a = sent[sent.length - 1]) != null ? _a : null,
1366
1416
  context: {
1367
1417
  ...context != null ? context : {},
1368
1418
  imageSources: sent.map((shot) => pageShot && shot === pageShot ? "page" : "attached")
@@ -1386,8 +1436,8 @@ function AssistSheet({
1386
1436
  setConversationId(result.conversationId);
1387
1437
  onConversationChange == null ? void 0 : onConversationChange(result.conversationId);
1388
1438
  }
1389
- setDuplicate((_b = result.duplicate) != null ? _b : null);
1390
- onDuplicateChange == null ? void 0 : onDuplicateChange((_d = (_c = result.duplicate) == null ? void 0 : _c.number) != null ? _d : null);
1439
+ setDuplicate((_a = result.duplicate) != null ? _a : null);
1440
+ onDuplicateChange == null ? void 0 : onDuplicateChange((_c = (_b = result.duplicate) == null ? void 0 : _b.number) != null ? _c : null);
1391
1441
  if (result.solved) {
1392
1442
  setSolved(result.solved);
1393
1443
  setOptions([]);
@@ -1405,7 +1455,7 @@ function AssistSheet({
1405
1455
  }
1406
1456
  if (result.question) {
1407
1457
  setMessages([...history2, { role: "assistant", content: result.question }]);
1408
- setOptions((_e = result.options) != null ? _e : []);
1458
+ setOptions((_d = result.options) != null ? _d : []);
1409
1459
  return;
1410
1460
  }
1411
1461
  onDegrade("The assistant had nothing to add \u2014 write your report below and send it as normal.");
@@ -1424,7 +1474,14 @@ function AssistSheet({
1424
1474
  const value = text.trim();
1425
1475
  if (!value || busy) return;
1426
1476
  setOptions([]);
1427
- const history2 = [...messages, { role: "user", content: value }];
1477
+ const underDraft = phase === "draft";
1478
+ if (underDraft) {
1479
+ const edited = aiDraftRef.current !== null && description.trim() !== aiDraftRef.current.trim();
1480
+ logEvent("keep_chatting", edited ? "after editing" : void 0);
1481
+ setPhase("chat");
1482
+ }
1483
+ const base = underDraft ? withCurrentDraft(messages) : messages;
1484
+ const history2 = [...base, { role: "user", content: value }];
1428
1485
  setMessages(history2);
1429
1486
  setInput("");
1430
1487
  void runTurn(history2);
@@ -1433,27 +1490,16 @@ function AssistSheet({
1433
1490
  onPickType(tile);
1434
1491
  setPhase(tile === "RATING" ? "rating" : "chat");
1435
1492
  }
1436
- function keepChatting() {
1493
+ function withCurrentDraft(history2) {
1437
1494
  const current = description.trim();
1438
- logEvent(
1439
- "keep_chatting",
1440
- aiDraftRef.current && current !== aiDraftRef.current.trim() ? "after editing" : void 0
1441
- );
1442
- setMessages((prev) => {
1443
- const next = [...prev];
1444
- for (let i = next.length - 1; i >= 0; i--) {
1445
- if (next[i].kind === "report") {
1446
- if (current) next[i] = { ...next[i], content: current };
1447
- break;
1448
- }
1495
+ const next = [...history2];
1496
+ for (let i = next.length - 1; i >= 0; i--) {
1497
+ if (next[i].kind === "report") {
1498
+ if (current) next[i] = { ...next[i], content: current };
1499
+ break;
1449
1500
  }
1450
- return next;
1451
- });
1452
- setPhase("chat");
1453
- requestAnimationFrame(() => {
1454
- var _a;
1455
- return (_a = inputRef.current) == null ? void 0 : _a.focus();
1456
- });
1501
+ }
1502
+ return next;
1457
1503
  }
1458
1504
  function sendDraft() {
1459
1505
  const edited = aiDraftRef.current !== null && description.trim() !== aiDraftRef.current.trim();
@@ -1461,9 +1507,39 @@ function AssistSheet({
1461
1507
  flushEvents();
1462
1508
  onSend();
1463
1509
  }
1464
- const lastMessage = messages[messages.length - 1];
1465
- const correcting = phase === "chat" && (lastMessage == null ? void 0 : lastMessage.kind) === "report";
1466
1510
  const empty = phase === "chat" && messages.length === 0 && !busy;
1511
+ function renderKnownIssue({ title, footer }) {
1512
+ if (!duplicate) return null;
1513
+ return /* @__PURE__ */ jsxs2(
1514
+ "div",
1515
+ {
1516
+ "data-gg-known-issue": "",
1517
+ style: {
1518
+ display: "flex",
1519
+ flexDirection: "column",
1520
+ gap: "3px",
1521
+ padding: "9px 10px",
1522
+ borderRadius: "8px",
1523
+ border: "1px solid rgba(245,158,11,0.4)",
1524
+ backgroundColor: "rgba(245,158,11,0.08)",
1525
+ fontSize: "12px",
1526
+ lineHeight: 1.5,
1527
+ minWidth: 0
1528
+ },
1529
+ children: [
1530
+ /* @__PURE__ */ jsx2("span", { style: { color: "#f59e0b", fontWeight: 600 }, children: title ? "Our team is already on this" : `Our team is already on #${duplicate.number}` }),
1531
+ title && /* @__PURE__ */ jsxs2("span", { style: { color: t2.textMuted, wordBreak: "break-word" }, children: [
1532
+ "#",
1533
+ duplicate.number,
1534
+ " ",
1535
+ duplicate.title
1536
+ ] }),
1537
+ duplicate.status && /* @__PURE__ */ jsx2("span", { style: { color: t2.text, fontWeight: 600 }, children: duplicate.status }),
1538
+ footer && /* @__PURE__ */ jsx2("span", { style: { color: t2.textMuted }, children: footer })
1539
+ ]
1540
+ }
1541
+ );
1542
+ }
1467
1543
  const initials = (reporterName != null ? reporterName : "").split(/\s+/).filter(Boolean).slice(0, 2).map((w) => {
1468
1544
  var _a, _b;
1469
1545
  return (_b = (_a = w[0]) == null ? void 0 : _a.toUpperCase()) != null ? _b : "";
@@ -1488,7 +1564,7 @@ function AssistSheet({
1488
1564
  @keyframes gg-sheet-up{from{transform:translateY(24px);opacity:0}to{transform:translateY(0);opacity:1}}
1489
1565
  @keyframes gg-sheet-fade{from{opacity:0}to{opacity:1}}
1490
1566
  @keyframes gg-dot{0%,80%,100%{transform:translateY(0);opacity:.4}40%{transform:translateY(-3px);opacity:1}}
1491
- [data-gg-sheet-send]:focus-visible,[data-gg-chip]:focus-visible{outline:2px solid currentColor;outline-offset:2px}
1567
+ [data-gg-sheet-send]:focus-visible,[data-gg-chip]:focus-visible,[data-gg-assist-remove]:focus-visible{outline:2px solid currentColor;outline-offset:2px}
1492
1568
  ` }),
1493
1569
  /* @__PURE__ */ jsx2(
1494
1570
  "div",
@@ -1954,6 +2030,7 @@ function AssistSheet({
1954
2030
  i
1955
2031
  );
1956
2032
  }),
2033
+ duplicate && !busy && phase === "chat" && /* @__PURE__ */ jsx2("div", { style: { paddingLeft: "32px", minWidth: 0 }, children: renderKnownIssue({ title: false }) }),
1957
2034
  options.length > 0 && !busy && phase === "chat" && /* @__PURE__ */ jsx2(
1958
2035
  "div",
1959
2036
  {
@@ -2052,35 +2129,13 @@ function AssistSheet({
2052
2129
  textTransform: "uppercase",
2053
2130
  color: t2.accent
2054
2131
  },
2055
- children: "Your report \xB7 edit anything"
2056
- }
2057
- ),
2058
- duplicate && /* @__PURE__ */ jsxs2(
2059
- "div",
2060
- {
2061
- style: {
2062
- display: "flex",
2063
- flexDirection: "column",
2064
- gap: "3px",
2065
- padding: "9px 10px",
2066
- borderRadius: "8px",
2067
- border: "1px solid rgba(245,158,11,0.4)",
2068
- backgroundColor: "rgba(245,158,11,0.08)",
2069
- fontSize: "12px",
2070
- lineHeight: 1.5
2071
- },
2072
- children: [
2073
- /* @__PURE__ */ jsx2("span", { style: { color: "#f59e0b", fontWeight: 600 }, children: "Our team is already on this" }),
2074
- /* @__PURE__ */ jsxs2("span", { style: { color: t2.textMuted, wordBreak: "break-word" }, children: [
2075
- "#",
2076
- duplicate.number,
2077
- " ",
2078
- duplicate.title
2079
- ] }),
2080
- /* @__PURE__ */ jsx2("span", { style: { color: t2.textMuted }, children: "Your details will be added to it instead of opening a new issue." })
2081
- ]
2132
+ children: duplicate ? `What we'll add to #${duplicate.number} \xB7 edit anything` : "Your report \xB7 edit anything"
2082
2133
  }
2083
2134
  ),
2135
+ renderKnownIssue({
2136
+ title: true,
2137
+ footer: "Your details will be added to it instead of opening a new issue."
2138
+ }),
2084
2139
  /* @__PURE__ */ jsx2(
2085
2140
  "textarea",
2086
2141
  {
@@ -2175,26 +2230,7 @@ function AssistSheet({
2175
2230
  " ",
2176
2231
  attachmentCount === 1 ? "attachment" : "attachments",
2177
2232
  " will be sent with this."
2178
- ] }),
2179
- /* @__PURE__ */ jsx2(
2180
- "button",
2181
- {
2182
- type: "button",
2183
- onClick: keepChatting,
2184
- style: {
2185
- alignSelf: "flex-start",
2186
- border: "none",
2187
- background: "transparent",
2188
- color: t2.textMuted,
2189
- fontSize: "12px",
2190
- fontFamily: "inherit",
2191
- padding: "9px 0",
2192
- cursor: "pointer",
2193
- textDecoration: "underline"
2194
- },
2195
- children: "Not quite \u2014 keep chatting"
2196
- }
2197
- )
2233
+ ] })
2198
2234
  ]
2199
2235
  }
2200
2236
  ),
@@ -2298,189 +2334,270 @@ function AssistSheet({
2298
2334
  },
2299
2335
  children: isSubmitting ? "Sending\u2026" : rating < 1 ? "Pick a star first" : "Send Rating"
2300
2336
  }
2301
- ) : phase === "draft" ? /* @__PURE__ */ jsxs2(Fragment, { children: [
2302
- validationError && /* @__PURE__ */ jsx2(
2303
- "p",
2304
- {
2305
- style: {
2306
- margin: "0 0 8px",
2307
- color: "#ef4444",
2308
- fontSize: "12px"
2309
- },
2310
- children: validationError
2311
- }
2312
- ),
2313
- /* @__PURE__ */ jsx2(
2314
- "button",
2315
- {
2316
- type: "button",
2317
- "data-gg-sheet-send": "",
2318
- onClick: sendDraft,
2319
- disabled: isSubmitting || !description.trim(),
2320
- style: {
2321
- width: "100%",
2322
- padding: "12px",
2323
- borderRadius: "10px",
2324
- border: "none",
2325
- backgroundColor: isSubmitting || !description.trim() ? t2.bgSecondary : t2.accent,
2326
- color: isSubmitting || !description.trim() ? t2.textMuted : t2.accentText,
2327
- fontSize: "14px",
2328
- fontWeight: 700,
2329
- fontFamily: "inherit",
2330
- cursor: isSubmitting || !description.trim() ? "default" : "pointer"
2331
- },
2332
- children: isSubmitting ? "Sending\u2026" : duplicate ? `Add to #${duplicate.number}` : "Send Report"
2333
- }
2334
- )
2335
- ] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
2336
- (screenshots.length > 0 || onAddImages) && /* @__PURE__ */ jsxs2(
2337
- "div",
2338
- {
2339
- style: {
2340
- display: "flex",
2341
- gap: "8px",
2342
- alignItems: "center",
2343
- flexWrap: "wrap",
2344
- marginBottom: "8px"
2345
- },
2346
- children: [
2347
- screenshots.map((shot, i) => {
2348
- const read = i >= screenshots.length - MAX_ASSIST_IMAGES;
2349
- return /* @__PURE__ */ jsx2(
2350
- "img",
2337
+ ) : (
2338
+ // Chat AND draft share this branch, so the chat box is the same
2339
+ // element on both sides of a draft landing — it stays put
2340
+ // instead of remounting, and a draft is something you can
2341
+ // still reply to (#360), not a form that replaced the chat.
2342
+ /* @__PURE__ */ jsxs2(Fragment, { children: [
2343
+ (screenshots.length > 0 || onAddImages) && /* @__PURE__ */ jsxs2(
2344
+ "div",
2345
+ {
2346
+ style: {
2347
+ display: "flex",
2348
+ gap: "8px",
2349
+ alignItems: "center",
2350
+ flexWrap: "wrap",
2351
+ marginBottom: "8px"
2352
+ },
2353
+ children: [
2354
+ screenshots.map((shot, i) => {
2355
+ const read = readShots.has(shot);
2356
+ return /* @__PURE__ */ jsxs2(
2357
+ "div",
2358
+ {
2359
+ style: {
2360
+ position: "relative",
2361
+ width: "40px",
2362
+ height: "40px",
2363
+ flexShrink: 0
2364
+ },
2365
+ children: [
2366
+ /* @__PURE__ */ jsx2(
2367
+ "img",
2368
+ {
2369
+ src: shot,
2370
+ alt: read ? "Attached image the assistant reads" : "Attached image",
2371
+ "data-gg-assist-thumb": "",
2372
+ style: {
2373
+ display: "block",
2374
+ width: "100%",
2375
+ height: "100%",
2376
+ objectFit: "cover",
2377
+ borderRadius: "8px",
2378
+ border: `1px solid ${read ? t2.accent : t2.inputBorder}`,
2379
+ opacity: read ? 1 : 0.45,
2380
+ boxSizing: "border-box"
2381
+ }
2382
+ }
2383
+ ),
2384
+ onRemoveImage && /* @__PURE__ */ jsx2(
2385
+ "button",
2386
+ {
2387
+ type: "button",
2388
+ "data-gg-assist-remove": "",
2389
+ "aria-label": `Remove image ${i + 1}`,
2390
+ title: "Remove this image",
2391
+ onClick: () => {
2392
+ logEvent(
2393
+ "image_removed",
2394
+ pageShot && shot === pageShot ? "page capture" : "attached"
2395
+ );
2396
+ onRemoveImage(i);
2397
+ },
2398
+ style: {
2399
+ position: "absolute",
2400
+ top: "-13px",
2401
+ right: "-13px",
2402
+ width: "32px",
2403
+ height: "32px",
2404
+ border: "none",
2405
+ background: "transparent",
2406
+ padding: 0,
2407
+ cursor: "pointer",
2408
+ display: "flex",
2409
+ alignItems: "center",
2410
+ justifyContent: "center",
2411
+ borderRadius: "50%"
2412
+ },
2413
+ children: /* @__PURE__ */ jsx2(
2414
+ "span",
2415
+ {
2416
+ "aria-hidden": "true",
2417
+ style: {
2418
+ width: "20px",
2419
+ height: "20px",
2420
+ boxSizing: "border-box",
2421
+ borderRadius: "50%",
2422
+ border: `2px solid ${t2.bg}`,
2423
+ background: "#ef4444",
2424
+ color: "#fff",
2425
+ display: "flex",
2426
+ alignItems: "center",
2427
+ justifyContent: "center"
2428
+ },
2429
+ children: /* @__PURE__ */ jsx2("svg", { width: "8", height: "8", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ jsx2(
2430
+ "path",
2431
+ {
2432
+ d: "M1 1L9 9M9 1L1 9",
2433
+ stroke: "currentColor",
2434
+ strokeWidth: "1.6",
2435
+ strokeLinecap: "round"
2436
+ }
2437
+ ) })
2438
+ }
2439
+ )
2440
+ }
2441
+ )
2442
+ ]
2443
+ },
2444
+ `${i}-${shot.slice(-16)}`
2445
+ );
2446
+ }),
2447
+ onAddImages && /* @__PURE__ */ jsxs2(
2448
+ "label",
2351
2449
  {
2352
- src: shot,
2353
- alt: read ? "Attached image the assistant reads" : "Attached image",
2354
- "data-gg-assist-thumb": "",
2450
+ "data-gg-assist-attach": "",
2451
+ title: "Attach an image \u2014 or just paste one",
2355
2452
  style: {
2356
2453
  width: "40px",
2357
2454
  height: "40px",
2358
- objectFit: "cover",
2359
2455
  borderRadius: "8px",
2360
- border: `1px solid ${read ? t2.accent : t2.inputBorder}`,
2361
- opacity: read ? 1 : 0.45
2362
- }
2363
- },
2364
- `${i}-${shot.slice(-16)}`
2365
- );
2366
- }),
2367
- onAddImages && /* @__PURE__ */ jsxs2(
2368
- "label",
2369
- {
2370
- "data-gg-assist-attach": "",
2371
- title: "Attach an image \u2014 or just paste one",
2372
- style: {
2373
- width: "40px",
2374
- height: "40px",
2375
- borderRadius: "8px",
2376
- border: `1px dashed ${t2.inputBorder}`,
2377
- display: "inline-flex",
2378
- alignItems: "center",
2379
- justifyContent: "center",
2380
- color: t2.textMuted,
2381
- fontSize: "18px",
2382
- cursor: "pointer",
2383
- flexShrink: 0
2384
- },
2385
- children: [
2386
- "+",
2387
- /* @__PURE__ */ jsx2(
2388
- "input",
2389
- {
2390
- type: "file",
2391
- accept: "image/*",
2392
- multiple: true,
2393
- onChange: (e) => {
2394
- var _a;
2395
- const files = Array.from((_a = e.target.files) != null ? _a : []);
2396
- if (files.length) {
2397
- logEvent("image_added", String(files.length));
2398
- onAddImages(files);
2399
- }
2400
- e.target.value = "";
2401
- },
2402
- style: { display: "none" }
2403
- }
2404
- )
2405
- ]
2406
- }
2407
- ),
2408
- screenshots.length > MAX_ASSIST_IMAGES && /* @__PURE__ */ jsxs2("span", { style: { color: t2.textMuted, fontSize: "11px" }, children: [
2409
- "newest ",
2410
- MAX_ASSIST_IMAGES,
2411
- " read"
2412
- ] })
2413
- ]
2414
- }
2415
- ),
2416
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: "8px", alignItems: "flex-end" }, children: [
2417
- /* @__PURE__ */ jsx2(
2418
- "textarea",
2419
- {
2420
- ref: inputRef,
2421
- value: input,
2422
- onChange: (e) => setInput(e.target.value),
2423
- onKeyDown: (e) => {
2424
- if (e.key === "Enter" && !e.shiftKey) {
2425
- e.preventDefault();
2426
- send(input);
2427
- }
2428
- },
2429
- placeholder: correcting ? "What should change in the draft?" : "Type your answer\u2026",
2430
- rows: 1,
2431
- disabled: busy,
2432
- autoFocus: true,
2433
- style: {
2434
- flex: 1,
2435
- minHeight: "42px",
2436
- maxHeight: "120px",
2437
- padding: "11px 12px",
2438
- borderRadius: "10px",
2439
- border: `1px solid ${t2.inputBorder}`,
2440
- backgroundColor: t2.inputBg,
2441
- color: t2.text,
2442
- fontSize: "13px",
2443
- lineHeight: 1.4,
2444
- fontFamily: "inherit",
2445
- resize: "none",
2446
- outline: "none",
2447
- boxSizing: "border-box"
2448
- }
2456
+ border: `1px dashed ${t2.inputBorder}`,
2457
+ display: "inline-flex",
2458
+ alignItems: "center",
2459
+ justifyContent: "center",
2460
+ color: t2.textMuted,
2461
+ fontSize: "18px",
2462
+ cursor: "pointer",
2463
+ flexShrink: 0
2464
+ },
2465
+ children: [
2466
+ "+",
2467
+ /* @__PURE__ */ jsx2(
2468
+ "input",
2469
+ {
2470
+ type: "file",
2471
+ accept: "image/*",
2472
+ multiple: true,
2473
+ onChange: (e) => {
2474
+ var _a;
2475
+ const files = Array.from((_a = e.target.files) != null ? _a : []);
2476
+ if (files.length) {
2477
+ logEvent("image_added", String(files.length));
2478
+ onAddImages(files);
2479
+ }
2480
+ e.target.value = "";
2481
+ },
2482
+ style: { display: "none" }
2483
+ }
2484
+ )
2485
+ ]
2486
+ }
2487
+ ),
2488
+ readShots.size < screenshots.length && /* @__PURE__ */ jsxs2("span", { style: { color: t2.textMuted, fontSize: "11px" }, children: [
2489
+ readShots.size,
2490
+ " of ",
2491
+ screenshots.length,
2492
+ " read"
2493
+ ] })
2494
+ ]
2449
2495
  }
2450
2496
  ),
2451
- /* @__PURE__ */ jsx2(
2452
- "button",
2453
- {
2454
- type: "button",
2455
- "data-gg-sheet-send": "",
2456
- onClick: () => send(input),
2457
- disabled: busy || !input.trim(),
2458
- "aria-label": "Send message",
2459
- "data-gg-send-message": "",
2460
- style: {
2461
- flexShrink: 0,
2462
- width: "42px",
2463
- height: "42px",
2464
- borderRadius: "10px",
2465
- border: "none",
2466
- display: "inline-flex",
2467
- alignItems: "center",
2468
- justifyContent: "center",
2469
- backgroundColor: busy || !input.trim() ? t2.bgSecondary : t2.accent,
2470
- color: busy || !input.trim() ? t2.textMuted : t2.accentText,
2471
- cursor: busy || !input.trim() ? "default" : "pointer"
2472
- },
2473
- children: /* @__PURE__ */ jsx2("svg", { width: "17", height: "17", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(
2474
- "path",
2475
- {
2476
- d: "M3 20.5L21.5 12 3 3.5V10l12 2-12 2v6.5z",
2477
- fill: "currentColor"
2497
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: "8px", alignItems: "flex-end" }, children: [
2498
+ /* @__PURE__ */ jsx2(
2499
+ "textarea",
2500
+ {
2501
+ value: input,
2502
+ onChange: (e) => setInput(e.target.value),
2503
+ onKeyDown: (e) => {
2504
+ if (e.key === "Enter" && !e.shiftKey) {
2505
+ e.preventDefault();
2506
+ send(input);
2507
+ }
2508
+ },
2509
+ placeholder: phase === "draft" ? "Anything to change? Tell me here\u2026" : "Type your answer\u2026",
2510
+ rows: 1,
2511
+ disabled: busy,
2512
+ autoFocus: true,
2513
+ style: {
2514
+ flex: 1,
2515
+ minHeight: "42px",
2516
+ maxHeight: "120px",
2517
+ padding: "11px 12px",
2518
+ borderRadius: "10px",
2519
+ border: `1px solid ${t2.inputBorder}`,
2520
+ backgroundColor: t2.inputBg,
2521
+ color: t2.text,
2522
+ fontSize: "13px",
2523
+ lineHeight: 1.4,
2524
+ fontFamily: "inherit",
2525
+ resize: "none",
2526
+ outline: "none",
2527
+ boxSizing: "border-box"
2478
2528
  }
2479
- ) })
2480
- }
2481
- )
2529
+ }
2530
+ ),
2531
+ /* @__PURE__ */ jsx2(
2532
+ "button",
2533
+ {
2534
+ type: "button",
2535
+ "data-gg-sheet-send": "",
2536
+ onClick: () => send(input),
2537
+ disabled: busy || !input.trim(),
2538
+ "aria-label": "Send message",
2539
+ "data-gg-send-message": "",
2540
+ style: {
2541
+ flexShrink: 0,
2542
+ width: "42px",
2543
+ height: "42px",
2544
+ borderRadius: "10px",
2545
+ border: "none",
2546
+ display: "inline-flex",
2547
+ alignItems: "center",
2548
+ justifyContent: "center",
2549
+ backgroundColor: busy || !input.trim() ? t2.bgSecondary : t2.accent,
2550
+ color: busy || !input.trim() ? t2.textMuted : t2.accentText,
2551
+ cursor: busy || !input.trim() ? "default" : "pointer"
2552
+ },
2553
+ children: /* @__PURE__ */ jsx2("svg", { width: "17", height: "17", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(
2554
+ "path",
2555
+ {
2556
+ d: "M3 20.5L21.5 12 3 3.5V10l12 2-12 2v6.5z",
2557
+ fill: "currentColor"
2558
+ }
2559
+ ) })
2560
+ }
2561
+ )
2562
+ ] }),
2563
+ phase === "draft" && /* @__PURE__ */ jsxs2(Fragment, { children: [
2564
+ validationError && /* @__PURE__ */ jsx2(
2565
+ "p",
2566
+ {
2567
+ style: {
2568
+ margin: "8px 0 0",
2569
+ color: "#ef4444",
2570
+ fontSize: "12px"
2571
+ },
2572
+ children: validationError
2573
+ }
2574
+ ),
2575
+ /* @__PURE__ */ jsx2(
2576
+ "button",
2577
+ {
2578
+ type: "button",
2579
+ "data-gg-sheet-send": "",
2580
+ onClick: sendDraft,
2581
+ disabled: isSubmitting || !description.trim(),
2582
+ style: {
2583
+ width: "100%",
2584
+ marginTop: "8px",
2585
+ padding: "12px",
2586
+ borderRadius: "10px",
2587
+ border: "none",
2588
+ backgroundColor: isSubmitting || !description.trim() ? t2.bgSecondary : t2.accent,
2589
+ color: isSubmitting || !description.trim() ? t2.textMuted : t2.accentText,
2590
+ fontSize: "14px",
2591
+ fontWeight: 700,
2592
+ fontFamily: "inherit",
2593
+ cursor: isSubmitting || !description.trim() ? "default" : "pointer"
2594
+ },
2595
+ children: isSubmitting ? "Sending\u2026" : duplicate ? `Add to #${duplicate.number}` : "Send Report"
2596
+ }
2597
+ )
2598
+ ] })
2482
2599
  ] })
2483
- ] }),
2600
+ ),
2484
2601
  phase === "chat" && messages.length > 0 && /* @__PURE__ */ jsx2(
2485
2602
  "button",
2486
2603
  {
@@ -3139,20 +3256,23 @@ function ReportDialog({
3139
3256
  e.target.value = "";
3140
3257
  };
3141
3258
  const addFiles = (files) => {
3142
- files.forEach((file) => {
3259
+ const images = files.filter((file) => file.type.startsWith("image/"));
3260
+ if (images.length > 0) {
3261
+ void Promise.all(images.map((file) => encodeImageFile(file).catch(() => null))).then(
3262
+ (encoded) => {
3263
+ const added = encoded.filter((url) => !!url);
3264
+ if (added.length > 0) setScreenshots((prev) => [...prev, ...added]);
3265
+ }
3266
+ );
3267
+ }
3268
+ files.filter((file) => !file.type.startsWith("image/")).forEach((file) => {
3143
3269
  const reader = new FileReader();
3144
- if (file.type.startsWith("image/")) {
3145
- reader.onload = () => {
3146
- setScreenshots((prev) => [...prev, reader.result]);
3147
- };
3148
- } else {
3149
- reader.onload = () => {
3150
- setAttachments((prev) => [
3151
- ...prev,
3152
- { name: file.name, size: file.size, dataUrl: reader.result }
3153
- ]);
3154
- };
3155
- }
3270
+ reader.onload = () => {
3271
+ setAttachments((prev) => [
3272
+ ...prev,
3273
+ { name: file.name, size: file.size, dataUrl: reader.result }
3274
+ ]);
3275
+ };
3156
3276
  reader.readAsDataURL(file);
3157
3277
  });
3158
3278
  };
@@ -5392,6 +5512,7 @@ function ReportDialog({
5392
5512
  screenshots,
5393
5513
  pageShot,
5394
5514
  onAddImages: addFiles,
5515
+ onRemoveImage: (index) => setScreenshots((prev) => prev.filter((_, i) => i !== index)),
5395
5516
  attachmentCount: screenshots.length + attachments.length,
5396
5517
  context: { ...assistContext != null ? assistContext : {}, reportType },
5397
5518
  reportTypeLabel: getTypeLabel(reportType),