glitchgrab 1.43.0 → 1.44.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
@@ -634,7 +634,11 @@ async function sendFeedback(payload, baseUrl) {
634
634
  return null;
635
635
  }
636
636
  }
637
- async function enhanceText(text, token, baseUrl, screenshot, context) {
637
+ function signedFields(identity) {
638
+ if (!(identity == null ? void 0 : identity.signature) || !identity.userId) return {};
639
+ return { userId: identity.userId, signature: identity.signature };
640
+ }
641
+ async function enhanceText(text, token, baseUrl, screenshot, context, identity) {
638
642
  var _a;
639
643
  try {
640
644
  const trimmed = text.trim();
@@ -649,7 +653,8 @@ async function enhanceText(text, token, baseUrl, screenshot, context) {
649
653
  body: JSON.stringify({
650
654
  text: trimmed,
651
655
  ...screenshot ? { screenshot } : {},
652
- ...context ? { context } : {}
656
+ ...context ? { context } : {},
657
+ ...signedFields(identity)
653
658
  })
654
659
  });
655
660
  if (!response.ok) return text;
@@ -662,6 +667,26 @@ async function enhanceText(text, token, baseUrl, screenshot, context) {
662
667
  return text;
663
668
  }
664
669
  }
670
+ async function findSimilarIssues(description, token, baseUrl, identity) {
671
+ var _a;
672
+ try {
673
+ const url = `${baseUrl != null ? baseUrl : DEFAULT_BASE_URL}/api/v1/reports/similar`;
674
+ const response = await fetch(url, {
675
+ method: "POST",
676
+ headers: {
677
+ "Content-Type": "application/json",
678
+ Authorization: `Bearer ${token}`
679
+ },
680
+ body: JSON.stringify({ text: description, ...signedFields(identity) })
681
+ });
682
+ const envelope = await response.json().catch(() => null);
683
+ if (!response.ok || !(envelope == null ? void 0 : envelope.success)) return null;
684
+ if (!((_a = envelope.data) == null ? void 0 : _a.enabled)) return [];
685
+ return Array.isArray(envelope.data.matches) ? envelope.data.matches : [];
686
+ } catch (e) {
687
+ return null;
688
+ }
689
+ }
665
690
  async function assistTurn(params, token, baseUrl) {
666
691
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
667
692
  const offline = {
@@ -703,13 +728,18 @@ async function assistTurn(params, token, baseUrl) {
703
728
  return offline;
704
729
  }
705
730
  }
706
- async function transcribeAudio(blob, token, baseUrl) {
731
+ async function transcribeAudio(blob, token, baseUrl, identity) {
707
732
  var _a;
708
733
  try {
709
734
  if (blob.size === 0) return "";
710
735
  const url = `${baseUrl != null ? baseUrl : DEFAULT_BASE_URL}/api/v1/sdk/stt`;
711
736
  const form = new FormData();
712
737
  form.append("file", blob, "audio.webm");
738
+ const signed = signedFields(identity);
739
+ if (signed.userId && signed.signature) {
740
+ form.append("userId", signed.userId);
741
+ form.append("signature", signed.signature);
742
+ }
713
743
  const response = await fetch(url, {
714
744
  method: "POST",
715
745
  headers: { Authorization: `Bearer ${token}` },
@@ -1614,7 +1644,7 @@ function AssistSheet({
1614
1644
  minWidth: 0
1615
1645
  },
1616
1646
  children: [
1617
- /* @__PURE__ */ jsx2("span", { style: { color: "#f59e0b", fontWeight: 600 }, children: title ? "Our team is already on this" : `Our team is already on #${duplicate.number}` }),
1647
+ /* @__PURE__ */ jsx2("span", { style: { color: t2.text, fontWeight: 600 }, children: title ? "Our team is already on this" : `Our team is already on #${duplicate.number}` }),
1618
1648
  title && /* @__PURE__ */ jsxs2("span", { style: { color: t2.textMuted, wordBreak: "break-word" }, children: [
1619
1649
  "#",
1620
1650
  duplicate.number,
@@ -3238,6 +3268,27 @@ function captureDefaultScreenshot(options) {
3238
3268
  }
3239
3269
  var SEVERITY_REQUIRED = "Pick how bad it is before sending.";
3240
3270
  var SHOT_WAIT_MS = 3e3;
3271
+ var SIMILAR_WAIT_MS = 2500;
3272
+ var MAX_SIMILAR_SHOWN = 3;
3273
+ function ButtonSpinner() {
3274
+ return /* @__PURE__ */ jsx4(
3275
+ "span",
3276
+ {
3277
+ "aria-hidden": "true",
3278
+ style: {
3279
+ display: "inline-block",
3280
+ width: "11px",
3281
+ height: "11px",
3282
+ marginRight: "6px",
3283
+ verticalAlign: "-1px",
3284
+ border: "2px solid currentColor",
3285
+ borderRightColor: "transparent",
3286
+ borderRadius: "50%",
3287
+ animation: "gg-spin .7s linear infinite"
3288
+ }
3289
+ }
3290
+ );
3291
+ }
3241
3292
  function detectDark() {
3242
3293
  if (typeof window === "undefined") return true;
3243
3294
  try {
@@ -3544,6 +3595,7 @@ function ReportDialog({
3544
3595
  sendFeedback: sendFeedback2,
3545
3596
  enhanceText: enhanceText2,
3546
3597
  assist,
3598
+ findSimilarIssues: findSimilarIssues2,
3547
3599
  assistContext = null,
3548
3600
  transcribeAudio: transcribeAudio2,
3549
3601
  types,
@@ -3563,6 +3615,11 @@ function ReportDialog({
3563
3615
  const [selfReportPrefill, setSelfReportPrefill] = useState4("");
3564
3616
  const [typeChosen, setTypeChosen] = useState4(false);
3565
3617
  const [duplicateIssueNumber, setDuplicateIssueNumber] = useState4(null);
3618
+ const [similarIssues, setSimilarIssues] = useState4(null);
3619
+ const [checkingSimilar, setCheckingSimilar] = useState4(false);
3620
+ const [attachingTo, setAttachingTo] = useState4(null);
3621
+ const [pickedSimilar, setPickedSimilar] = useState4(null);
3622
+ const [addedToIssue, setAddedToIssue] = useState4(null);
3566
3623
  const [aiConversationId, setAiConversationId] = useState4(null);
3567
3624
  const [isEnhanced, setIsEnhanced] = useState4(false);
3568
3625
  const [originalDescription, setOriginalDescription] = useState4(
@@ -3993,6 +4050,10 @@ function ReportDialog({
3993
4050
  setReportType("BUG");
3994
4051
  setTypeChosen(false);
3995
4052
  setDuplicateIssueNumber(null);
4053
+ setSimilarIssues(null);
4054
+ setPickedSimilar(null);
4055
+ setCheckingSimilar(false);
4056
+ setAttachingTo(null);
3996
4057
  setSeverity(null);
3997
4058
  setValidationError(null);
3998
4059
  setSelfReportFrom(null);
@@ -4180,7 +4241,8 @@ function ReportDialog({
4180
4241
  stopListeningAndTranscribe();
4181
4242
  }
4182
4243
  };
4183
- const handleSubmit = async () => {
4244
+ const handleSubmit = async (opts) => {
4245
+ var _a2;
4184
4246
  try {
4185
4247
  if (isSubmitting) return;
4186
4248
  if (isRating) {
@@ -4217,6 +4279,22 @@ function ReportDialog({
4217
4279
  setStep(2);
4218
4280
  return;
4219
4281
  }
4282
+ const duplicateOf = (_a2 = opts == null ? void 0 : opts.duplicateOf) != null ? _a2 : duplicateIssueNumber;
4283
+ if (findSimilarIssues2 && !(opts == null ? void 0 : opts.skipSimilar) && !duplicateOf && !aiConversationId) {
4284
+ setIsSubmitting(true);
4285
+ setCheckingSimilar(true);
4286
+ const matches = await Promise.race([
4287
+ findSimilarIssues2(description.trim()).catch(() => null),
4288
+ new Promise((resolve) => setTimeout(() => resolve(null), SIMILAR_WAIT_MS))
4289
+ ]);
4290
+ setCheckingSimilar(false);
4291
+ if (matches && matches.length > 0) {
4292
+ setSimilarIssues(matches.slice(0, MAX_SIMILAR_SHOWN));
4293
+ setPickedSimilar(null);
4294
+ setIsSubmitting(false);
4295
+ return;
4296
+ }
4297
+ }
4220
4298
  setIsSubmitting(true);
4221
4299
  let shots = screenshots;
4222
4300
  const pending = pendingShotRef.current;
@@ -4235,8 +4313,8 @@ function ReportDialog({
4235
4313
  if (showSeverity && reportType === "BUG" && severity) {
4236
4314
  metadata.severity = severity;
4237
4315
  }
4238
- if (duplicateIssueNumber) {
4239
- metadata.duplicateIssueNumber = String(duplicateIssueNumber);
4316
+ if (duplicateOf) {
4317
+ metadata.duplicateIssueNumber = String(duplicateOf);
4240
4318
  }
4241
4319
  if (aiConversationId) {
4242
4320
  metadata.aiConversationId = aiConversationId;
@@ -4249,20 +4327,26 @@ function ReportDialog({
4249
4327
  Object.keys(metadata).length > 0 ? metadata : void 0
4250
4328
  );
4251
4329
  if (result) {
4330
+ setAddedToIssue(duplicateOf && result.issueNumber === duplicateOf ? duplicateOf : null);
4252
4331
  setSubmitted(true);
4253
4332
  setDescription("");
4254
4333
  setScreenshots([]);
4255
4334
  setAttachments([]);
4256
4335
  setAiConversationId(null);
4257
4336
  setDuplicateIssueNumber(null);
4337
+ setSimilarIssues(null);
4338
+ setPickedSimilar(null);
4258
4339
  setTimeout(() => {
4259
4340
  setSubmitted(false);
4260
4341
  handleClose();
4261
4342
  }, 2e3);
4262
4343
  }
4263
4344
  setIsSubmitting(false);
4345
+ setAttachingTo(null);
4264
4346
  } catch (e) {
4265
4347
  setIsSubmitting(false);
4348
+ setCheckingSimilar(false);
4349
+ setAttachingTo(null);
4266
4350
  }
4267
4351
  };
4268
4352
  if (!mounted) return null;
@@ -4270,6 +4354,7 @@ function ReportDialog({
4270
4354
  isOpen && createPortal3(
4271
4355
  /* @__PURE__ */ jsx4("style", { children: `
4272
4356
  @keyframes gg-pulse{0%,100%{opacity:1}50%{opacity:0.35}}
4357
+ @keyframes gg-spin{to{transform:rotate(360deg)}}
4273
4358
  @keyframes gg-b1{0%,100%{height:3px}40%{height:13px}}
4274
4359
  @keyframes gg-b2{0%,100%{height:8px}50%{height:3px}}
4275
4360
  @keyframes gg-b3{0%,100%{height:4px}30%{height:14px}70%{height:5px}}
@@ -4554,7 +4639,7 @@ function ReportDialog({
4554
4639
  flex: "1 1 auto",
4555
4640
  minHeight: 0
4556
4641
  },
4557
- children: submitted ? /* @__PURE__ */ jsxs4(
4642
+ children: submitted ? /* @__PURE__ */ jsx4(
4558
4643
  "div",
4559
4644
  {
4560
4645
  style: {
@@ -4564,10 +4649,7 @@ function ReportDialog({
4564
4649
  fontSize: "14px",
4565
4650
  fontWeight: 500
4566
4651
  },
4567
- children: [
4568
- getTypeLabel(reportType),
4569
- " sent. Thank you!"
4570
- ]
4652
+ children: addedToIssue ? `Added to #${addedToIssue}. Thank you!` : `${getTypeLabel(reportType)} sent. Thank you!`
4571
4653
  }
4572
4654
  ) : /* @__PURE__ */ jsxs4(Fragment3, { children: [
4573
4655
  step === 1 && headerSlot ? /* @__PURE__ */ jsx4("div", { style: { marginBottom: "14px" }, children: headerSlot }) : null,
@@ -5792,11 +5874,194 @@ function ReportDialog({
5792
5874
  children: validationError
5793
5875
  }
5794
5876
  ),
5795
- /* @__PURE__ */ jsx4(
5877
+ similarIssues ? (
5878
+ // Same visual language as the assistant's "Our team is
5879
+ // already on this" card, so both routes to a duplicate
5880
+ // read as one feature.
5881
+ /* @__PURE__ */ jsxs4(
5882
+ "div",
5883
+ {
5884
+ "data-gg-similar": "",
5885
+ style: {
5886
+ marginTop: "12px",
5887
+ display: "flex",
5888
+ flexDirection: "column",
5889
+ gap: "10px",
5890
+ padding: "12px",
5891
+ borderRadius: "10px",
5892
+ border: "1px solid rgba(245,158,11,0.4)",
5893
+ backgroundColor: "rgba(245,158,11,0.08)"
5894
+ },
5895
+ children: [
5896
+ /* @__PURE__ */ jsxs4("div", { style: { display: "flex", flexDirection: "column", gap: "2px" }, children: [
5897
+ /* @__PURE__ */ jsx4("span", { style: { color: t2.text, fontWeight: 600, fontSize: "13px" }, children: "Our team may already have this" }),
5898
+ /* @__PURE__ */ jsx4("span", { style: { color: t2.textMuted, fontSize: "12px", lineHeight: 1.5 }, children: similarIssues.length > 1 ? "Pick the one that matches \u2014 your report is added to it, so the team sees everyone hitting it in one place." : "If it's the same problem, pick it \u2014 your report is added to it, so the team sees everyone hitting it in one place." })
5899
+ ] }),
5900
+ /* @__PURE__ */ jsx4(
5901
+ "div",
5902
+ {
5903
+ role: "radiogroup",
5904
+ "aria-label": "Existing issues",
5905
+ style: { display: "flex", flexDirection: "column", gap: "6px" },
5906
+ children: similarIssues.map((issue) => {
5907
+ const picked = pickedSimilar === issue.number;
5908
+ return /* @__PURE__ */ jsxs4(
5909
+ "button",
5910
+ {
5911
+ type: "button",
5912
+ role: "radio",
5913
+ "aria-checked": picked,
5914
+ disabled: isSubmitting,
5915
+ onClick: () => setPickedSimilar(issue.number),
5916
+ style: {
5917
+ display: "flex",
5918
+ alignItems: "flex-start",
5919
+ gap: "10px",
5920
+ width: "100%",
5921
+ minHeight: "44px",
5922
+ padding: "9px 10px",
5923
+ borderRadius: "8px",
5924
+ border: `1px solid ${picked ? t2.accent : t2.border}`,
5925
+ boxShadow: picked ? `0 0 0 1px ${t2.accent}` : "none",
5926
+ backgroundColor: t2.bg,
5927
+ color: t2.text,
5928
+ textAlign: "left",
5929
+ cursor: isSubmitting ? "not-allowed" : "pointer",
5930
+ fontFamily: "inherit"
5931
+ },
5932
+ children: [
5933
+ /* @__PURE__ */ jsx4(
5934
+ "span",
5935
+ {
5936
+ "aria-hidden": "true",
5937
+ style: {
5938
+ flexShrink: 0,
5939
+ marginTop: "2px",
5940
+ width: "14px",
5941
+ height: "14px",
5942
+ boxSizing: "border-box",
5943
+ borderRadius: "50%",
5944
+ border: `2px solid ${picked ? t2.accent : t2.textMuted}`,
5945
+ backgroundColor: picked ? t2.accent : "transparent",
5946
+ boxShadow: picked ? `inset 0 0 0 2px ${t2.bg}` : "none"
5947
+ }
5948
+ }
5949
+ ),
5950
+ /* @__PURE__ */ jsxs4(
5951
+ "span",
5952
+ {
5953
+ style: {
5954
+ display: "flex",
5955
+ flexDirection: "column",
5956
+ gap: "3px",
5957
+ minWidth: 0
5958
+ },
5959
+ children: [
5960
+ /* @__PURE__ */ jsxs4(
5961
+ "span",
5962
+ {
5963
+ title: issue.title,
5964
+ style: {
5965
+ fontSize: "12.5px",
5966
+ lineHeight: 1.45,
5967
+ wordBreak: "break-word",
5968
+ overflow: "hidden",
5969
+ display: "-webkit-box",
5970
+ WebkitLineClamp: 3,
5971
+ WebkitBoxOrient: "vertical"
5972
+ },
5973
+ children: [
5974
+ /* @__PURE__ */ jsxs4("span", { style: { color: t2.textMuted, fontWeight: 600 }, children: [
5975
+ "#",
5976
+ issue.number
5977
+ ] }),
5978
+ " ",
5979
+ issue.title
5980
+ ]
5981
+ }
5982
+ ),
5983
+ issue.status && /* @__PURE__ */ jsx4("span", { style: { fontSize: "11.5px", color: t2.textMuted }, children: issue.status })
5984
+ ]
5985
+ }
5986
+ )
5987
+ ]
5988
+ },
5989
+ issue.number
5990
+ );
5991
+ })
5992
+ }
5993
+ ),
5994
+ /* @__PURE__ */ jsx4(
5995
+ "button",
5996
+ {
5997
+ type: "button",
5998
+ disabled: isSubmitting || !pickedSimilar,
5999
+ onClick: () => {
6000
+ if (!pickedSimilar) return;
6001
+ setAttachingTo(pickedSimilar);
6002
+ setDuplicateIssueNumber(pickedSimilar);
6003
+ void handleSubmit({ duplicateOf: pickedSimilar });
6004
+ },
6005
+ style: {
6006
+ width: "100%",
6007
+ minHeight: "40px",
6008
+ padding: "10px",
6009
+ borderRadius: "8px",
6010
+ border: "none",
6011
+ backgroundColor: isSubmitting || !pickedSimilar ? t2.bgSecondary : t2.accent,
6012
+ color: isSubmitting || !pickedSimilar ? t2.textMuted : t2.accentText,
6013
+ fontSize: "14px",
6014
+ fontWeight: 600,
6015
+ cursor: isSubmitting || !pickedSimilar ? "not-allowed" : "pointer",
6016
+ fontFamily: "inherit"
6017
+ },
6018
+ children: typeof attachingTo === "number" ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
6019
+ /* @__PURE__ */ jsx4(ButtonSpinner, {}),
6020
+ "Adding to #",
6021
+ attachingTo,
6022
+ "\u2026"
6023
+ ] }) : pickedSimilar ? `Add to #${pickedSimilar}` : "Pick the matching issue"
6024
+ }
6025
+ ),
6026
+ /* @__PURE__ */ jsx4(
6027
+ "button",
6028
+ {
6029
+ type: "button",
6030
+ disabled: isSubmitting,
6031
+ onClick: () => {
6032
+ setAttachingTo("new");
6033
+ void handleSubmit({ skipSimilar: true });
6034
+ },
6035
+ style: {
6036
+ width: "100%",
6037
+ minHeight: "40px",
6038
+ marginTop: "-4px",
6039
+ padding: "8px",
6040
+ borderRadius: "8px",
6041
+ border: "none",
6042
+ backgroundColor: "transparent",
6043
+ color: isSubmitting ? t2.textMuted : t2.text,
6044
+ fontSize: "13px",
6045
+ fontWeight: 500,
6046
+ textDecoration: "underline",
6047
+ textUnderlineOffset: "3px",
6048
+ cursor: isSubmitting ? "not-allowed" : "pointer",
6049
+ fontFamily: "inherit"
6050
+ },
6051
+ children: attachingTo === "new" ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
6052
+ /* @__PURE__ */ jsx4(ButtonSpinner, {}),
6053
+ "Sending as new\u2026"
6054
+ ] }) : "No, mine is different \u2014 send as new"
6055
+ }
6056
+ )
6057
+ ]
6058
+ }
6059
+ )
6060
+ ) : /* @__PURE__ */ jsx4(
5796
6061
  "button",
5797
6062
  {
5798
6063
  type: "button",
5799
- onClick: handleSubmit,
6064
+ onClick: () => void handleSubmit(),
5800
6065
  disabled: submitDisabled,
5801
6066
  style: {
5802
6067
  marginTop: "12px",
@@ -5812,7 +6077,10 @@ function ReportDialog({
5812
6077
  fontFamily: "inherit",
5813
6078
  transition: "background-color 0.15s ease"
5814
6079
  },
5815
- children: isSubmitting ? shotPending && !isRating ? "Attaching screenshot\u2026" : "Sending..." : isRating ? "Send Rating" : "Send Report"
6080
+ children: checkingSimilar ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
6081
+ /* @__PURE__ */ jsx4(ButtonSpinner, {}),
6082
+ "Checking for existing reports\u2026"
6083
+ ] }) : isSubmitting ? shotPending && !isRating ? "Attaching screenshot\u2026" : "Sending..." : isRating ? "Send Rating" : "Send Report"
5816
6084
  }
5817
6085
  )
5818
6086
  ] })
@@ -6124,7 +6392,7 @@ function ReportDialog({
6124
6392
  severityRefused: needsSeverity,
6125
6393
  isSubmitting,
6126
6394
  submitted,
6127
- onSend: () => void handleSubmit(),
6395
+ onSend: () => void handleSubmit({ skipSimilar: true }),
6128
6396
  onReportGlitchgrabProblem: reportGlitchgrabProblem ? (prefill) => {
6129
6397
  setSelfReportPrefill(prefill != null ? prefill : "");
6130
6398
  setSelfReportFrom("assist");
@@ -7149,6 +7417,12 @@ function GlitchgrabProviderInner({
7149
7417
  },
7150
7418
  []
7151
7419
  );
7420
+ const signedUserId = session == null ? void 0 : session.userId;
7421
+ const signature = typeof (session == null ? void 0 : session.signature) === "string" ? session.signature : null;
7422
+ const identity = useMemo3(
7423
+ () => ({ userId: signedUserId != null ? signedUserId : null, signature }),
7424
+ [signedUserId, signature]
7425
+ );
7152
7426
  const enhance = useCallback2(
7153
7427
  async (text, screenshot) => {
7154
7428
  try {
@@ -7158,12 +7432,12 @@ function GlitchgrabProviderInner({
7158
7432
  visitedPages: ctx.visitedPages.slice(-5),
7159
7433
  breadcrumbs: ctx.breadcrumbs.slice(-10).map((b) => ({ type: b.type, message: b.message }))
7160
7434
  };
7161
- return await enhanceText(text, token, baseUrl, screenshot, context);
7435
+ return await enhanceText(text, token, baseUrl, screenshot, context, identity);
7162
7436
  } catch (e) {
7163
7437
  return text;
7164
7438
  }
7165
7439
  },
7166
- [token, baseUrl]
7440
+ [token, baseUrl, identity]
7167
7441
  );
7168
7442
  const assist = useCallback2(
7169
7443
  async (params) => {
@@ -7176,7 +7450,8 @@ function GlitchgrabProviderInner({
7176
7450
  reporter: session ? {
7177
7451
  key: session.userId,
7178
7452
  name: session.name ? String(session.name) : null,
7179
- email: session.email ? String(session.email) : null
7453
+ email: session.email ? String(session.email) : null,
7454
+ ...signature ? { signature } : {}
7180
7455
  } : void 0,
7181
7456
  context: {
7182
7457
  ...(_a = params.context) != null ? _a : {},
@@ -7191,15 +7466,19 @@ function GlitchgrabProviderInner({
7191
7466
  },
7192
7467
  [token, baseUrl, session]
7193
7468
  );
7469
+ const findSimilar = useCallback2(
7470
+ (description) => findSimilarIssues(description, token, baseUrl, identity),
7471
+ [token, baseUrl, identity]
7472
+ );
7194
7473
  const transcribe = useCallback2(
7195
7474
  async (blob) => {
7196
7475
  try {
7197
- return await transcribeAudio(blob, token, baseUrl);
7476
+ return await transcribeAudio(blob, token, baseUrl, identity);
7198
7477
  } catch (e) {
7199
7478
  return "";
7200
7479
  }
7201
7480
  },
7202
- [token, baseUrl]
7481
+ [token, baseUrl, identity]
7203
7482
  );
7204
7483
  const openReportDialog = useCallback2((options) => {
7205
7484
  if (typeof window !== "undefined") {
@@ -7217,6 +7496,8 @@ function GlitchgrabProviderInner({
7217
7496
  const [projectName, setProjectName] = useState6(null);
7218
7497
  const [aiAssist, setAiAssist] = useState6(false);
7219
7498
  const [glitchgrabReport, setGlitchgrabReport] = useState6(false);
7499
+ const [signedUsers, setSignedUsers] = useState6("OFF");
7500
+ const aiBlockedUnsigned = signedUsers === "ENFORCE" && !signature;
7220
7501
  const [bookingOpen, setBookingOpen] = useState6(false);
7221
7502
  const openBookingDialog = useCallback2(() => setBookingOpen(true), []);
7222
7503
  useEffect6(() => {
@@ -7229,12 +7510,25 @@ function GlitchgrabProviderInner({
7229
7510
  setProjectName(json.data.name);
7230
7511
  setAiAssist(json.data.aiAssist === true);
7231
7512
  setGlitchgrabReport(json.data.glitchgrabReport === true);
7513
+ const mode = json.data.signedUsers;
7514
+ setSignedUsers(mode === "WARN" || mode === "ENFORCE" ? mode : "OFF");
7232
7515
  }).catch(() => {
7233
7516
  });
7234
7517
  return () => {
7235
7518
  cancelled = true;
7236
7519
  };
7237
7520
  }, [baseUrl, token]);
7521
+ useEffect6(() => {
7522
+ var _a;
7523
+ try {
7524
+ if (signedUsers === "OFF" || signature || !session) return;
7525
+ if (typeof process === "undefined" || ((_a = process.env) == null ? void 0 : _a.NODE_ENV) !== "development") return;
7526
+ console.warn(
7527
+ signedUsers === "ENFORCE" ? '[glitchgrab] This project enforces signed users and `session.signature` is missing \u2014 AI features are hidden. Sign it on your server with signSession() from "glitchgrab/server".' : '[glitchgrab] This project warns on unsigned users and `session.signature` is missing. Sign it on your server with signSession() from "glitchgrab/server" before the owner switches to Enforce.'
7528
+ );
7529
+ } catch (e) {
7530
+ }
7531
+ }, [signedUsers, signature, session]);
7238
7532
  useEffect6(() => {
7239
7533
  try {
7240
7534
  if (typeof window === "undefined") return;
@@ -7309,10 +7603,11 @@ function GlitchgrabProviderInner({
7309
7603
  {
7310
7604
  report,
7311
7605
  sendFeedback: feedback,
7312
- enhanceText: enhance,
7313
- assist: aiAssist ? assist : void 0,
7606
+ enhanceText: aiAssist && !aiBlockedUnsigned ? enhance : void 0,
7607
+ assist: aiAssist && !aiBlockedUnsigned ? assist : void 0,
7608
+ findSimilarIssues: aiAssist && !aiBlockedUnsigned ? findSimilar : void 0,
7314
7609
  reportGlitchgrabProblem: glitchgrabReport ? reportGlitchgrabProblem : void 0,
7315
- transcribeAudio: transcribe,
7610
+ transcribeAudio: aiBlockedUnsigned ? void 0 : transcribe,
7316
7611
  types,
7317
7612
  showSeverity,
7318
7613
  reporter,