spaps-issue-reporting-react 0.4.2 → 0.6.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
@@ -1,7 +1,6 @@
1
1
  // src/components.tsx
2
2
  import * as Dialog from "@radix-ui/react-dialog";
3
3
  import * as Popover from "@radix-ui/react-popover";
4
- import { useScribe } from "@elevenlabs/react";
5
4
  import {
6
5
  BugBeetle,
7
6
  CheckCircle,
@@ -508,6 +507,7 @@ function IssueReportingProvider({
508
507
  inputModes,
509
508
  defaultInputMode,
510
509
  voice,
510
+ screenshotCapture,
511
511
  copy,
512
512
  children
513
513
  }) {
@@ -753,6 +753,7 @@ function IssueReportingProvider({
753
753
  inputModes: resolvedInputModes,
754
754
  defaultInputMode: resolvedDefaultInputMode,
755
755
  voice: resolvedVoiceConfig,
756
+ screenshotCapture,
756
757
  needsResponseIssueIds,
757
758
  setNeedsResponse
758
759
  }),
@@ -781,6 +782,7 @@ function IssueReportingProvider({
781
782
  resolvedInputModes,
782
783
  resolvedVoiceConfig,
783
784
  scope,
785
+ screenshotCapture,
784
786
  selectPanel,
785
787
  setNeedsResponse,
786
788
  startNewIssue
@@ -837,6 +839,9 @@ var POPOVER_SHADOW = "shadow-[0_18px_48px_rgba(15,23,42,0.18)]";
837
839
  var MODAL_SHADOW = "shadow-[0_28px_80px_rgba(15,23,42,0.24)]";
838
840
  var BADGE_TEXT = "text-[11px]";
839
841
  var LABEL_TEXT = "text-[11px]";
842
+ var VoiceCapture = React2.lazy(() => import("./VoiceCapture-WX7J6BWQ.mjs"));
843
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect2 : React2.useLayoutEffect;
844
+ var SCREENSHOT_CAPTURE_METADATA_KEY = "screenshot_capture";
840
845
  function truncate(value, max = 80) {
841
846
  if (value.length <= max) {
842
847
  return value;
@@ -857,8 +862,13 @@ function resolveErrorMessage(error, fallback) {
857
862
  }
858
863
  return fallback;
859
864
  }
860
- function getCommittedTranscriptText(committedTranscripts) {
861
- return committedTranscripts.map((segment) => segment.text.trim()).filter(Boolean).join(" ").trim();
865
+ function buildScreenshotCaptureMetadata(config, status) {
866
+ return {
867
+ status,
868
+ scope: config.scope ?? "visible_viewport",
869
+ image_type: config.imageType ?? "image/png",
870
+ ...status === "failed" ? { failure_reason: "capture_failed" } : {}
871
+ };
862
872
  }
863
873
  function appendTranscriptToNote(current, transcript) {
864
874
  const normalizedCurrent = current.trim();
@@ -899,22 +909,14 @@ function getIssueOriginText(issue, copy) {
899
909
  const humanName = issue.reporter_display_name?.trim();
900
910
  return humanName ? `${copy.originHumanLabel} \xB7 ${humanName}` : copy.originHumanLabel;
901
911
  }
902
- function IssueReportVoicePanel({
912
+ function IssueReportVoiceActivationPanel({
903
913
  canUseText,
904
914
  effectiveInputMode,
905
915
  isSubmitting,
906
- isVoiceActive,
907
- isVoiceConnecting,
908
- voice,
909
- voiceTokenResult,
910
- committedTranscript,
911
- voiceError,
912
- scribeError,
913
916
  onSelectText,
914
917
  onSelectVoice,
915
- onStartVoiceInput,
916
- onStopVoiceInput,
917
- onAppendTranscript
918
+ onRequestStartVoiceInput,
919
+ onRequestAppendTranscript
918
920
  }) {
919
921
  return /* @__PURE__ */ jsxs(Fragment, { children: [
920
922
  canUseText ? /* @__PURE__ */ jsxs("div", { className: "mt-5 flex gap-2", children: [
@@ -951,53 +953,85 @@ function IssueReportVoicePanel({
951
953
  }
952
954
  )
953
955
  ] }) : null,
954
- /* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: [
955
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
956
- /* @__PURE__ */ jsxs("div", { children: [
957
- /* @__PURE__ */ jsx2("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: "Voice Input" }),
958
- /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-slate-600", children: [
959
- "Provider: ",
960
- voiceTokenResult?.provider ?? voice.provider,
961
- " \xB7 Model:",
962
- " ",
963
- voiceTokenResult?.model_id ?? voice.modelId
964
- ] }),
965
- /* @__PURE__ */ jsx2("p", { className: "mt-1 text-xs text-slate-500", children: voice.requireMicrophonePermission ? "Microphone access is required to transcribe." : "Microphone access policy is optional." })
966
- ] }),
967
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
968
- isVoiceActive || isVoiceConnecting ? /* @__PURE__ */ jsx2(
969
- "button",
970
- {
971
- type: "button",
972
- className: "rounded-full border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-100",
973
- onClick: onStopVoiceInput,
974
- disabled: isSubmitting,
975
- children: "Stop Voice Input"
976
- }
977
- ) : /* @__PURE__ */ jsx2(
978
- "button",
979
- {
980
- type: "button",
981
- className: "rounded-full bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-slate-800",
982
- onClick: onStartVoiceInput,
983
- disabled: isSubmitting,
984
- children: "Start Voice Input"
985
- }
986
- ),
987
- canUseText ? /* @__PURE__ */ jsx2(
988
- "button",
989
- {
990
- type: "button",
991
- className: "rounded-full border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-60",
992
- onClick: onAppendTranscript,
993
- disabled: isSubmitting || !committedTranscript.trim(),
994
- children: "Append Transcript"
995
- }
996
- ) : null
997
- ] })
956
+ /* @__PURE__ */ jsx2("div", { className: "mt-4 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
957
+ /* @__PURE__ */ jsxs("div", { children: [
958
+ /* @__PURE__ */ jsx2("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: "Voice Input" }),
959
+ /* @__PURE__ */ jsx2("p", { className: "mt-1 text-xs text-slate-600", children: "Voice capture loads when selected." })
998
960
  ] }),
999
- /* @__PURE__ */ jsx2("div", { className: "mt-3 rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-700", children: committedTranscript ? committedTranscript : /* @__PURE__ */ jsx2("span", { className: "text-slate-500", children: "No committed transcript yet." }) }),
1000
- voiceError || scribeError ? /* @__PURE__ */ jsx2("div", { className: "mt-2 rounded-xl border border-rose-200 bg-rose-50 px-3 py-2 text-xs text-rose-700", children: voiceError ?? scribeError }) : isVoiceConnecting ? /* @__PURE__ */ jsx2("div", { className: "mt-2 text-xs text-slate-500", children: "Connecting voice transcription..." }) : null
961
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
962
+ /* @__PURE__ */ jsx2(
963
+ "button",
964
+ {
965
+ type: "button",
966
+ className: "rounded-full bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-slate-800",
967
+ onClick: onRequestStartVoiceInput,
968
+ disabled: isSubmitting,
969
+ children: "Start Voice Input"
970
+ }
971
+ ),
972
+ canUseText ? /* @__PURE__ */ jsx2(
973
+ "button",
974
+ {
975
+ type: "button",
976
+ className: "rounded-full border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-60",
977
+ onClick: onRequestAppendTranscript,
978
+ disabled: isSubmitting,
979
+ children: "Append Transcript"
980
+ }
981
+ ) : null
982
+ ] })
983
+ ] }) })
984
+ ] });
985
+ }
986
+ function IssueReportVoiceFallback({
987
+ canUseText,
988
+ isSubmitting,
989
+ onSelectText,
990
+ onSelectVoice,
991
+ onRequestStartVoiceInput
992
+ }) {
993
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
994
+ canUseText ? /* @__PURE__ */ jsxs("div", { className: "mt-5 flex gap-2", children: [
995
+ /* @__PURE__ */ jsxs(
996
+ "button",
997
+ {
998
+ type: "button",
999
+ className: "inline-flex items-center gap-1 rounded-full border border-slate-200 px-3 py-1.5 text-xs font-semibold text-slate-700 transition hover:bg-slate-50",
1000
+ onClick: onSelectText,
1001
+ disabled: isSubmitting,
1002
+ children: [
1003
+ /* @__PURE__ */ jsx2(TextT, { className: "h-3.5 w-3.5" }),
1004
+ "Text Input"
1005
+ ]
1006
+ }
1007
+ ),
1008
+ /* @__PURE__ */ jsxs(
1009
+ "button",
1010
+ {
1011
+ type: "button",
1012
+ className: "inline-flex items-center gap-1 rounded-full border border-slate-900 bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white transition",
1013
+ onClick: onSelectVoice,
1014
+ disabled: isSubmitting,
1015
+ children: [
1016
+ /* @__PURE__ */ jsx2(Microphone, { className: "h-3.5 w-3.5" }),
1017
+ "Voice Input"
1018
+ ]
1019
+ }
1020
+ )
1021
+ ] }) : null,
1022
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-xs text-slate-600", children: [
1023
+ /* @__PURE__ */ jsx2(Spinner, { className: "h-3.5 w-3.5 animate-spin" }),
1024
+ /* @__PURE__ */ jsx2("span", { className: "flex-1", children: "Loading voice input..." }),
1025
+ /* @__PURE__ */ jsx2(
1026
+ "button",
1027
+ {
1028
+ type: "button",
1029
+ className: "rounded-full bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-slate-800",
1030
+ onClick: onRequestStartVoiceInput,
1031
+ disabled: isSubmitting,
1032
+ children: "Start Voice Input"
1033
+ }
1034
+ )
1001
1035
  ] })
1002
1036
  ] });
1003
1037
  }
@@ -1068,13 +1102,10 @@ function IssueReportModalBody({
1068
1102
  normalizedNote,
1069
1103
  isValid,
1070
1104
  isSubmitting,
1071
- isVoiceActive,
1072
- isVoiceConnecting,
1073
1105
  voice,
1074
- voiceTokenResult,
1075
- committedTranscript,
1076
- voiceError,
1077
- scribeError,
1106
+ createVoiceToken,
1107
+ voiceStartRequestId,
1108
+ voiceAppendRequestId,
1078
1109
  submitError,
1079
1110
  existingAttachments,
1080
1111
  removedExistingIds,
@@ -1085,8 +1116,10 @@ function IssueReportModalBody({
1085
1116
  onClose,
1086
1117
  onSelectText,
1087
1118
  onSelectVoice,
1088
- onStartVoiceInput,
1089
- onStopVoiceInput,
1119
+ onRequestStartVoiceInput,
1120
+ onRequestAppendTranscript,
1121
+ onTranscriptCommitted,
1122
+ onVoiceSubmitMetadataChange,
1090
1123
  onAppendTranscript,
1091
1124
  onNoteChange,
1092
1125
  onSubmit,
@@ -1126,24 +1159,47 @@ function IssueReportModalBody({
1126
1159
  ] });
1127
1160
  }
1128
1161
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1129
- mode === "create" && canUseVoice ? /* @__PURE__ */ jsx2(
1130
- IssueReportVoicePanel,
1162
+ mode === "create" && canUseVoice ? effectiveInputMode === "voice" ? /* @__PURE__ */ jsx2(
1163
+ React2.Suspense,
1164
+ {
1165
+ fallback: /* @__PURE__ */ jsx2(
1166
+ IssueReportVoiceFallback,
1167
+ {
1168
+ canUseText,
1169
+ isSubmitting,
1170
+ onSelectText,
1171
+ onSelectVoice,
1172
+ onRequestStartVoiceInput
1173
+ }
1174
+ ),
1175
+ children: /* @__PURE__ */ jsx2(
1176
+ VoiceCapture,
1177
+ {
1178
+ canUseText,
1179
+ effectiveInputMode,
1180
+ isSubmitting,
1181
+ voice,
1182
+ createVoiceToken,
1183
+ startRequestId: voiceStartRequestId,
1184
+ appendRequestId: voiceAppendRequestId,
1185
+ onSelectText,
1186
+ onSelectVoice,
1187
+ onTranscriptCommitted,
1188
+ onSubmitMetadataChange: onVoiceSubmitMetadataChange,
1189
+ onAppendTranscript
1190
+ }
1191
+ )
1192
+ }
1193
+ ) : /* @__PURE__ */ jsx2(
1194
+ IssueReportVoiceActivationPanel,
1131
1195
  {
1132
1196
  canUseText,
1133
1197
  effectiveInputMode,
1134
1198
  isSubmitting,
1135
- isVoiceActive,
1136
- isVoiceConnecting,
1137
- voice,
1138
- voiceTokenResult,
1139
- committedTranscript,
1140
- voiceError,
1141
- scribeError,
1142
1199
  onSelectText,
1143
1200
  onSelectVoice,
1144
- onStartVoiceInput,
1145
- onStopVoiceInput,
1146
- onAppendTranscript
1201
+ onRequestStartVoiceInput,
1202
+ onRequestAppendTranscript
1147
1203
  }
1148
1204
  ) : null,
1149
1205
  mode === "reply" && issue ? /* @__PURE__ */ jsxs("div", { className: "mt-5 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: [
@@ -1205,101 +1261,6 @@ function IssueReportModalBody({
1205
1261
  mode !== "create" && issue ? /* @__PURE__ */ jsx2(IssueReportMessageThread, { issueReportId: issue.id }) : null
1206
1262
  ] });
1207
1263
  }
1208
- function useIssueReportVoiceCapture({
1209
- canUseVoice,
1210
- defaultInputMode,
1211
- isSubmitting,
1212
- voice
1213
- }) {
1214
- const [inputMode, setInputMode] = useState2(defaultInputMode);
1215
- const [voiceTokenResult, setVoiceTokenResult] = useState2(null);
1216
- const [voiceSubmitMetadata, setVoiceSubmitMetadata] = useState2(null);
1217
- const [voiceError, setVoiceError] = useState2(null);
1218
- const {
1219
- status: scribeStatus,
1220
- isConnected,
1221
- isTranscribing,
1222
- committedTranscripts,
1223
- error: scribeError,
1224
- connect: connectScribe,
1225
- disconnect: disconnectScribe
1226
- } = useScribe({
1227
- autoConnect: false,
1228
- modelId: voice.modelId,
1229
- microphone: voice.microphone
1230
- });
1231
- const committedTranscript = useMemo2(
1232
- () => getCommittedTranscriptText(committedTranscripts),
1233
- [committedTranscripts]
1234
- );
1235
- const isVoiceConnecting = scribeStatus === "connecting";
1236
- const isVoiceActive = isConnected || isTranscribing;
1237
- const resetVoiceCapture = useCallback2(() => {
1238
- disconnectScribe();
1239
- setInputMode(defaultInputMode);
1240
- setVoiceTokenResult(null);
1241
- setVoiceSubmitMetadata(null);
1242
- setVoiceError(null);
1243
- }, [defaultInputMode, disconnectScribe]);
1244
- const startVoiceInput = useCallback2(
1245
- async (createVoiceToken) => {
1246
- if (!canUseVoice || isSubmitting || isVoiceActive || isVoiceConnecting) {
1247
- return;
1248
- }
1249
- if (!createVoiceToken) {
1250
- setVoiceError("Voice input is unavailable for this client.");
1251
- return;
1252
- }
1253
- setVoiceError(null);
1254
- try {
1255
- const tokenResult = await createVoiceToken();
1256
- setVoiceTokenResult(tokenResult);
1257
- setVoiceSubmitMetadata(tokenResult);
1258
- await connectScribe({
1259
- token: tokenResult.token,
1260
- modelId: tokenResult.model_id,
1261
- microphone: voice.microphone
1262
- });
1263
- setInputMode("voice");
1264
- } catch (startError) {
1265
- setVoiceError(resolveErrorMessage(startError, "Failed to start voice input."));
1266
- }
1267
- },
1268
- [
1269
- canUseVoice,
1270
- connectScribe,
1271
- isSubmitting,
1272
- isVoiceActive,
1273
- isVoiceConnecting,
1274
- voice.microphone
1275
- ]
1276
- );
1277
- const appendTranscript = useCallback2(
1278
- (setNote) => {
1279
- if (!committedTranscript.trim()) {
1280
- return;
1281
- }
1282
- setNote((current) => appendTranscriptToNote(current, committedTranscript));
1283
- setInputMode("text");
1284
- },
1285
- [committedTranscript]
1286
- );
1287
- return {
1288
- inputMode,
1289
- setInputMode,
1290
- voiceTokenResult,
1291
- voiceSubmitMetadata,
1292
- committedTranscript,
1293
- voiceError,
1294
- scribeError,
1295
- isVoiceConnecting,
1296
- isVoiceActive,
1297
- resetVoiceCapture,
1298
- startVoiceInput,
1299
- stopVoiceInput: disconnectScribe,
1300
- appendTranscript
1301
- };
1302
- }
1303
1264
  var INITIAL_UPLOAD_PROGRESS = {
1304
1265
  phase: "idle",
1305
1266
  uploaded: 0,
@@ -2079,11 +2040,18 @@ function IssueReportModal() {
2079
2040
  retryModalHydration,
2080
2041
  inputModes,
2081
2042
  defaultInputMode,
2082
- voice
2043
+ voice,
2044
+ screenshotCapture,
2045
+ createMode
2083
2046
  } = useIssueReporting();
2084
2047
  const { createMutation, updateMutation, replyMutation } = useIssueReportingMutations();
2085
2048
  const [note, setNote] = useState2("");
2086
2049
  const [submitError, setSubmitError] = useState2(null);
2050
+ const [inputMode, setInputMode] = useState2(defaultInputMode);
2051
+ const [committedTranscript, setCommittedTranscript] = useState2("");
2052
+ const [voiceSubmitMetadata, setVoiceSubmitMetadata] = useState2(null);
2053
+ const [voiceStartRequestId, setVoiceStartRequestId] = useState2(0);
2054
+ const [voiceAppendRequestId, setVoiceAppendRequestId] = useState2(0);
2087
2055
  const { isOpen, mode, issue, target, isHydrating, error } = modalState;
2088
2056
  const canUseVoice = mode === "create" && inputModes.includes("voice");
2089
2057
  const canUseText = mode !== "create" || inputModes.includes("text");
@@ -2094,27 +2062,14 @@ function IssueReportModal() {
2094
2062
  );
2095
2063
  const attachmentState = useAttachmentState(existingAttachments);
2096
2064
  const isSubmitting = createMutation.isPending || updateMutation.isPending || replyMutation.isPending || attachmentState.uploadProgress.phase === "uploading";
2097
- const {
2098
- inputMode,
2099
- setInputMode,
2100
- voiceTokenResult,
2101
- voiceSubmitMetadata,
2102
- committedTranscript,
2103
- voiceError,
2104
- scribeError,
2105
- isVoiceConnecting,
2106
- isVoiceActive,
2107
- resetVoiceCapture,
2108
- startVoiceInput,
2109
- stopVoiceInput,
2110
- appendTranscript
2111
- } = useIssueReportVoiceCapture({
2112
- canUseVoice,
2113
- defaultInputMode,
2114
- isSubmitting,
2115
- voice
2116
- });
2117
- useEffect2(() => {
2065
+ const resetVoiceCapture = useCallback2(() => {
2066
+ setInputMode(defaultInputMode);
2067
+ setCommittedTranscript("");
2068
+ setVoiceSubmitMetadata(null);
2069
+ setVoiceStartRequestId(0);
2070
+ setVoiceAppendRequestId(0);
2071
+ }, [defaultInputMode]);
2072
+ useIsomorphicLayoutEffect(() => {
2118
2073
  if (!isOpen) {
2119
2074
  resetVoiceCapture();
2120
2075
  attachmentState.reset();
@@ -2140,14 +2095,17 @@ function IssueReportModal() {
2140
2095
  attachmentState.reset();
2141
2096
  closeModal();
2142
2097
  };
2143
- const handleStartVoiceInput = async () => {
2144
- await startVoiceInput(client.issueReporting.createVoiceToken);
2098
+ const handleRequestAppendTranscript = () => {
2099
+ setInputMode("voice");
2100
+ setVoiceAppendRequestId((current) => current + 1);
2145
2101
  };
2146
- const handleStopVoiceInput = () => {
2147
- stopVoiceInput();
2102
+ const handleRequestStartVoiceInput = () => {
2103
+ setInputMode("voice");
2104
+ setVoiceStartRequestId((current) => current + 1);
2148
2105
  };
2149
- const handleAppendTranscript = () => {
2150
- appendTranscript(setNote);
2106
+ const handleAppendTranscript = (transcript) => {
2107
+ setNote((current) => appendTranscriptToNote(current, transcript));
2108
+ setInputMode("text");
2151
2109
  };
2152
2110
  const uploadPendingFiles = async () => {
2153
2111
  const upload = client.issueReporting.uploadAttachment;
@@ -2189,8 +2147,41 @@ function IssueReportModal() {
2189
2147
  try {
2190
2148
  const noteForSubmit = normalizedNote;
2191
2149
  let newAttachmentIds = [];
2150
+ let screenshotCaptureMetadata = null;
2151
+ if (screenshotCapture?.enabled && mode === "create" && canUseAttachments && client.issueReporting.uploadAttachment) {
2152
+ try {
2153
+ const { captureScreenshot } = await import("./screenshot-capture.mjs");
2154
+ const captureResult = await captureScreenshot(screenshotCapture, {
2155
+ pageUrl: target.page_url,
2156
+ createMode,
2157
+ inputMode: effectiveInputMode,
2158
+ target: {
2159
+ componentKey: target.component_key,
2160
+ componentLabel: target.component_label,
2161
+ surfaceRef: target.surface_ref,
2162
+ metadata: target.metadata
2163
+ }
2164
+ });
2165
+ const uploaded = await client.issueReporting.uploadAttachment(
2166
+ captureResult.blob,
2167
+ { filename: captureResult.filename }
2168
+ );
2169
+ newAttachmentIds.push(uploaded.id);
2170
+ screenshotCaptureMetadata = buildScreenshotCaptureMetadata(
2171
+ screenshotCapture,
2172
+ "attached"
2173
+ );
2174
+ } catch (captureError) {
2175
+ screenshotCaptureMetadata = buildScreenshotCaptureMetadata(
2176
+ screenshotCapture,
2177
+ "failed"
2178
+ );
2179
+ screenshotCapture.onCaptureError?.(captureError);
2180
+ }
2181
+ }
2192
2182
  if (canUseAttachments && attachmentState.pendingFiles.length > 0) {
2193
- newAttachmentIds = await uploadPendingFiles();
2183
+ const manualIds = await uploadPendingFiles();
2184
+ newAttachmentIds.push(...manualIds);
2194
2185
  }
2195
2186
  const effectiveVoiceMetadata = effectiveInputMode === "voice" ? voiceSubmitMetadata ?? {
2196
2187
  provider: voice.provider,
@@ -2201,20 +2192,26 @@ function IssueReportModal() {
2201
2192
  attach_transcript: true
2202
2193
  } : null;
2203
2194
  if (mode === "create") {
2195
+ const targetMetadata = {
2196
+ ...target.metadata ?? {},
2197
+ ...screenshotCaptureMetadata ? {
2198
+ [SCREENSHOT_CAPTURE_METADATA_KEY]: screenshotCaptureMetadata
2199
+ } : {},
2200
+ ...effectiveVoiceMetadata && effectiveInputMode === "voice" ? {
2201
+ capture: {
2202
+ input_mode: "voice",
2203
+ provider: effectiveVoiceMetadata.provider,
2204
+ model_id: effectiveVoiceMetadata.model_id,
2205
+ retain_audio: effectiveVoiceMetadata.retain_audio,
2206
+ attach_transcript: effectiveVoiceMetadata.attach_transcript
2207
+ }
2208
+ } : {}
2209
+ };
2204
2210
  await createMutation.mutateAsync({
2205
- target: effectiveVoiceMetadata && effectiveInputMode === "voice" ? {
2211
+ target: {
2206
2212
  ...target,
2207
- metadata: {
2208
- ...target.metadata ?? {},
2209
- capture: {
2210
- input_mode: "voice",
2211
- provider: effectiveVoiceMetadata.provider,
2212
- model_id: effectiveVoiceMetadata.model_id,
2213
- retain_audio: effectiveVoiceMetadata.retain_audio,
2214
- attach_transcript: effectiveVoiceMetadata.attach_transcript
2215
- }
2216
- }
2217
- } : target,
2213
+ metadata: targetMetadata
2214
+ },
2218
2215
  note: noteForSubmit,
2219
2216
  reporter_role_hint: reporterRoleHint,
2220
2217
  attachment_ids: newAttachmentIds.length > 0 ? newAttachmentIds : void 0
@@ -2287,13 +2284,10 @@ function IssueReportModal() {
2287
2284
  normalizedNote,
2288
2285
  isValid,
2289
2286
  isSubmitting,
2290
- isVoiceActive,
2291
- isVoiceConnecting,
2292
2287
  voice,
2293
- voiceTokenResult,
2294
- committedTranscript,
2295
- voiceError,
2296
- scribeError,
2288
+ createVoiceToken: client.issueReporting.createVoiceToken,
2289
+ voiceStartRequestId,
2290
+ voiceAppendRequestId,
2297
2291
  submitError,
2298
2292
  existingAttachments,
2299
2293
  removedExistingIds: attachmentState.removedExistingIds,
@@ -2304,8 +2298,10 @@ function IssueReportModal() {
2304
2298
  onClose: handleCloseModal,
2305
2299
  onSelectText: () => setInputMode("text"),
2306
2300
  onSelectVoice: () => setInputMode("voice"),
2307
- onStartVoiceInput: () => void handleStartVoiceInput(),
2308
- onStopVoiceInput: handleStopVoiceInput,
2301
+ onRequestStartVoiceInput: handleRequestStartVoiceInput,
2302
+ onRequestAppendTranscript: handleRequestAppendTranscript,
2303
+ onTranscriptCommitted: setCommittedTranscript,
2304
+ onVoiceSubmitMetadataChange: setVoiceSubmitMetadata,
2309
2305
  onAppendTranscript: handleAppendTranscript,
2310
2306
  onNoteChange: setNote,
2311
2307
  onSubmit: () => void handleSubmit(),
@@ -0,0 +1,19 @@
1
+ import { j as IssueReportingScreenshotCaptureRect, f as IssueReportingScreenshotCaptureConfig, h as IssueReportingScreenshotCaptureContext } from './types-D9U13O2X.mjs';
2
+ import 'react';
3
+ import 'spaps-types';
4
+
5
+ declare const SCREENSHOT_CAPTURE_REDACTION_TEXT = "[redacted]";
6
+ declare const ATTACHMENT_MAX_BYTES: number;
7
+ interface ScreenshotCaptureResult {
8
+ blob: Blob;
9
+ filename: string;
10
+ mimeType: string;
11
+ byteSize: number;
12
+ rect: IssueReportingScreenshotCaptureRect;
13
+ scaled: boolean;
14
+ }
15
+ declare function captureScreenshot(config: IssueReportingScreenshotCaptureConfig, context: IssueReportingScreenshotCaptureContext): Promise<ScreenshotCaptureResult>;
16
+ declare function resetHtml2CanvasCache(): void;
17
+ declare function maskScreenshotCaptureDocument(clonedDocument: Document, excludeSelectors?: readonly string[], maskSelectors?: readonly string[]): void;
18
+
19
+ export { ATTACHMENT_MAX_BYTES, IssueReportingScreenshotCaptureConfig, IssueReportingScreenshotCaptureContext, IssueReportingScreenshotCaptureRect, SCREENSHOT_CAPTURE_REDACTION_TEXT, type ScreenshotCaptureResult, captureScreenshot, maskScreenshotCaptureDocument, resetHtml2CanvasCache };
@@ -0,0 +1,19 @@
1
+ import { j as IssueReportingScreenshotCaptureRect, f as IssueReportingScreenshotCaptureConfig, h as IssueReportingScreenshotCaptureContext } from './types-D9U13O2X.js';
2
+ import 'react';
3
+ import 'spaps-types';
4
+
5
+ declare const SCREENSHOT_CAPTURE_REDACTION_TEXT = "[redacted]";
6
+ declare const ATTACHMENT_MAX_BYTES: number;
7
+ interface ScreenshotCaptureResult {
8
+ blob: Blob;
9
+ filename: string;
10
+ mimeType: string;
11
+ byteSize: number;
12
+ rect: IssueReportingScreenshotCaptureRect;
13
+ scaled: boolean;
14
+ }
15
+ declare function captureScreenshot(config: IssueReportingScreenshotCaptureConfig, context: IssueReportingScreenshotCaptureContext): Promise<ScreenshotCaptureResult>;
16
+ declare function resetHtml2CanvasCache(): void;
17
+ declare function maskScreenshotCaptureDocument(clonedDocument: Document, excludeSelectors?: readonly string[], maskSelectors?: readonly string[]): void;
18
+
19
+ export { ATTACHMENT_MAX_BYTES, IssueReportingScreenshotCaptureConfig, IssueReportingScreenshotCaptureContext, IssueReportingScreenshotCaptureRect, SCREENSHOT_CAPTURE_REDACTION_TEXT, type ScreenshotCaptureResult, captureScreenshot, maskScreenshotCaptureDocument, resetHtml2CanvasCache };