spaps-issue-reporting-react 0.1.3 → 0.1.5

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.js CHANGED
@@ -56,9 +56,10 @@ module.exports = __toCommonJS(index_exports);
56
56
  // src/components.tsx
57
57
  var Dialog = __toESM(require("@radix-ui/react-dialog"));
58
58
  var Popover = __toESM(require("@radix-ui/react-popover"));
59
- var import_react3 = require("@phosphor-icons/react");
59
+ var import_react3 = require("@elevenlabs/react");
60
+ var import_react4 = require("@phosphor-icons/react");
60
61
  var import_date_fns = require("date-fns");
61
- var import_react4 = __toESM(require("react"));
62
+ var import_react5 = __toESM(require("react"));
62
63
 
63
64
  // src/provider.tsx
64
65
  var import_react2 = require("react");
@@ -78,6 +79,9 @@ var import_jsx_runtime = require("react/jsx-runtime");
78
79
  var LIST_LIMIT = 200;
79
80
  var NOTE_MIN_LENGTH = 10;
80
81
  var NOTE_MAX_LENGTH = 2e3;
82
+ var DEFAULT_INPUT_MODES = ["text"];
83
+ var DEFAULT_VOICE_PROVIDER = "elevenlabs_scribe_realtime";
84
+ var DEFAULT_VOICE_MODEL_ID = "scribe_v2_realtime";
81
85
  var initialModalState = {
82
86
  isOpen: false,
83
87
  mode: "create",
@@ -170,6 +174,25 @@ function resolveInitialScope(defaultScope, allowTenantScope) {
170
174
  }
171
175
  return "mine";
172
176
  }
177
+ function normalizeInputModes(inputModes) {
178
+ if (!inputModes || inputModes.length === 0) {
179
+ return DEFAULT_INPUT_MODES;
180
+ }
181
+ const unique = Array.from(new Set(inputModes));
182
+ const supported = unique.filter(
183
+ (mode) => mode === "text" || mode === "voice"
184
+ );
185
+ if (supported.length === 0) {
186
+ return DEFAULT_INPUT_MODES;
187
+ }
188
+ return supported;
189
+ }
190
+ function resolveDefaultInputMode(defaultInputMode, inputModes) {
191
+ if (defaultInputMode && inputModes.includes(defaultInputMode)) {
192
+ return defaultInputMode;
193
+ }
194
+ return inputModes[0] ?? "text";
195
+ }
173
196
  function normalizeTarget(target, getPageUrl) {
174
197
  if (typeof target === "string") {
175
198
  const normalized = target.trim();
@@ -428,6 +451,9 @@ function IssueReportingProvider({
428
451
  defaultScope,
429
452
  allowTenantScope = false,
430
453
  defaultCreateMode = "general_page",
454
+ inputModes,
455
+ defaultInputMode,
456
+ voice,
431
457
  copy,
432
458
  children
433
459
  }) {
@@ -449,6 +475,23 @@ function IssueReportingProvider({
449
475
  const [registeredTargets, setRegisteredTargets] = (0, import_react2.useState)(
450
476
  []
451
477
  );
478
+ const resolvedInputModes = (0, import_react2.useMemo)(
479
+ () => normalizeInputModes(inputModes),
480
+ [inputModes]
481
+ );
482
+ const resolvedDefaultInputMode = (0, import_react2.useMemo)(
483
+ () => resolveDefaultInputMode(defaultInputMode, resolvedInputModes),
484
+ [defaultInputMode, resolvedInputModes]
485
+ );
486
+ const resolvedVoiceConfig = (0, import_react2.useMemo)(
487
+ () => ({
488
+ provider: voice?.provider ?? DEFAULT_VOICE_PROVIDER,
489
+ modelId: voice?.modelId ?? DEFAULT_VOICE_MODEL_ID,
490
+ requireMicrophonePermission: voice?.requireMicrophonePermission ?? true,
491
+ microphone: voice?.microphone
492
+ }),
493
+ [voice]
494
+ );
452
495
  (0, import_react2.useEffect)(() => {
453
496
  setScope(resolveInitialScope(defaultScope, allowTenantScope));
454
497
  }, [allowTenantScope, defaultScope]);
@@ -636,7 +679,10 @@ function IssueReportingProvider({
636
679
  scope,
637
680
  setScope,
638
681
  allowTenantScope,
639
- createMode
682
+ createMode,
683
+ inputModes: resolvedInputModes,
684
+ defaultInputMode: resolvedDefaultInputMode,
685
+ voice: resolvedVoiceConfig
640
686
  }),
641
687
  [
642
688
  allowTenantScope,
@@ -658,6 +704,9 @@ function IssueReportingProvider({
658
704
  principalId,
659
705
  reporterRoleHint,
660
706
  retryModalHydration,
707
+ resolvedDefaultInputMode,
708
+ resolvedInputModes,
709
+ resolvedVoiceConfig,
661
710
  scope,
662
711
  selectPanel,
663
712
  startNewIssue
@@ -702,6 +751,18 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
702
751
  function cn(...values) {
703
752
  return values.filter(Boolean).join(" ");
704
753
  }
754
+ var Z_FLOATING_BUTTON = "z-[65]";
755
+ var Z_BANNER = "z-[70]";
756
+ var Z_POPOVER = "z-[70]";
757
+ var Z_MODAL_OVERLAY = "z-[80]";
758
+ var Z_MODAL_CONTENT = "z-[81]";
759
+ var POPOVER_WIDTH = "w-[360px]";
760
+ var MODAL_WIDTH = "w-[calc(100vw-2rem)]";
761
+ var MODAL_RADIUS = "rounded-[28px]";
762
+ var POPOVER_SHADOW = "shadow-[0_18px_48px_rgba(15,23,42,0.18)]";
763
+ var MODAL_SHADOW = "shadow-[0_28px_80px_rgba(15,23,42,0.24)]";
764
+ var BADGE_TEXT = "text-[11px]";
765
+ var LABEL_TEXT = "text-[11px]";
705
766
  function truncate(value, max = 80) {
706
767
  if (value.length <= max) {
707
768
  return value;
@@ -722,6 +783,16 @@ function resolveErrorMessage(error, fallback) {
722
783
  }
723
784
  return fallback;
724
785
  }
786
+ function getCommittedTranscriptText(committedTranscripts) {
787
+ return committedTranscripts.map((segment) => segment.text.trim()).filter(Boolean).join(" ").trim();
788
+ }
789
+ function appendTranscriptToNote(current, transcript) {
790
+ const normalizedCurrent = current.trim();
791
+ if (!normalizedCurrent) {
792
+ return transcript;
793
+ }
794
+ return `${normalizedCurrent} ${transcript}`;
795
+ }
725
796
  function resolveReporterId(issue) {
726
797
  return issue.reporter_principal_id ?? issue.reporter_user_id ?? null;
727
798
  }
@@ -754,13 +825,390 @@ function getIssueOriginText(issue, copy) {
754
825
  const humanName = issue.reporter_display_name?.trim();
755
826
  return humanName ? `${copy.originHumanLabel} \xB7 ${humanName}` : copy.originHumanLabel;
756
827
  }
828
+ function IssueReportVoicePanel({
829
+ canUseText,
830
+ effectiveInputMode,
831
+ isSubmitting,
832
+ isVoiceActive,
833
+ isVoiceConnecting,
834
+ voice,
835
+ voiceTokenResult,
836
+ committedTranscript,
837
+ voiceError,
838
+ scribeError,
839
+ onSelectText,
840
+ onSelectVoice,
841
+ onStartVoiceInput,
842
+ onStopVoiceInput,
843
+ onAppendTranscript
844
+ }) {
845
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
846
+ canUseText ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 flex gap-2", children: [
847
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
848
+ "button",
849
+ {
850
+ type: "button",
851
+ className: cn(
852
+ "inline-flex items-center gap-1 rounded-full border px-3 py-1.5 text-xs font-semibold transition",
853
+ effectiveInputMode === "text" ? "border-slate-900 bg-slate-900 text-white" : "border-slate-200 text-slate-700 hover:bg-slate-50"
854
+ ),
855
+ onClick: onSelectText,
856
+ disabled: isSubmitting,
857
+ children: [
858
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.TextT, { className: "h-3.5 w-3.5" }),
859
+ "Text Input"
860
+ ]
861
+ }
862
+ ),
863
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
864
+ "button",
865
+ {
866
+ type: "button",
867
+ className: cn(
868
+ "inline-flex items-center gap-1 rounded-full border px-3 py-1.5 text-xs font-semibold transition",
869
+ effectiveInputMode === "voice" ? "border-slate-900 bg-slate-900 text-white" : "border-slate-200 text-slate-700 hover:bg-slate-50"
870
+ ),
871
+ onClick: onSelectVoice,
872
+ disabled: isSubmitting,
873
+ children: [
874
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Microphone, { className: "h-3.5 w-3.5" }),
875
+ "Voice Input"
876
+ ]
877
+ }
878
+ )
879
+ ] }) : null,
880
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-4 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: [
881
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
882
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
883
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: "Voice Input" }),
884
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "mt-1 text-xs text-slate-600", children: [
885
+ "Provider: ",
886
+ voiceTokenResult?.provider ?? voice.provider,
887
+ " \xB7 Model:",
888
+ " ",
889
+ voiceTokenResult?.model_id ?? voice.modelId
890
+ ] }),
891
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-1 text-xs text-slate-500", children: voice.requireMicrophonePermission ? "Microphone access is required to transcribe." : "Microphone access policy is optional." })
892
+ ] }),
893
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2", children: [
894
+ isVoiceActive || isVoiceConnecting ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
895
+ "button",
896
+ {
897
+ type: "button",
898
+ className: "rounded-full border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-100",
899
+ onClick: onStopVoiceInput,
900
+ disabled: isSubmitting,
901
+ children: "Stop Voice Input"
902
+ }
903
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
904
+ "button",
905
+ {
906
+ type: "button",
907
+ className: "rounded-full bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-slate-800",
908
+ onClick: onStartVoiceInput,
909
+ disabled: isSubmitting,
910
+ children: "Start Voice Input"
911
+ }
912
+ ),
913
+ canUseText ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
914
+ "button",
915
+ {
916
+ type: "button",
917
+ 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",
918
+ onClick: onAppendTranscript,
919
+ disabled: isSubmitting || !committedTranscript.trim(),
920
+ children: "Append Transcript"
921
+ }
922
+ ) : null
923
+ ] })
924
+ ] }),
925
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-slate-500", children: "No committed transcript yet." }) }),
926
+ voiceError || scribeError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-2 text-xs text-slate-500", children: "Connecting voice transcription..." }) : null
927
+ ] })
928
+ ] });
929
+ }
930
+ function IssueReportNoteEditor({
931
+ canUseText,
932
+ note,
933
+ normalizedNote,
934
+ isSubmitting,
935
+ copy,
936
+ onNoteChange,
937
+ onSubmit
938
+ }) {
939
+ if (!canUseText) {
940
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-xs text-slate-600", children: [
941
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: getIssueNoteLengthMessage(normalizedNote, copy) }),
942
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-1", children: "Submit is enabled after a committed transcript reaches 10-2000 characters." })
943
+ ] });
944
+ }
945
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 space-y-2", children: [
946
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
947
+ "textarea",
948
+ {
949
+ value: note,
950
+ onChange: (event) => onNoteChange(event.target.value),
951
+ onKeyDown: (event) => {
952
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
953
+ event.preventDefault();
954
+ onSubmit();
955
+ }
956
+ },
957
+ placeholder: copy.notePlaceholder,
958
+ className: "h-36 w-full resize-none rounded-2xl border border-slate-300 px-4 py-3 text-sm text-slate-800 outline-none transition focus:border-slate-500 focus:ring-2 focus:ring-slate-200",
959
+ disabled: isSubmitting,
960
+ autoFocus: true
961
+ }
962
+ ),
963
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between text-xs text-slate-500", children: [
964
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: getIssueNoteLengthMessage(note, copy) }),
965
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copy.keyboardShortcutHint })
966
+ ] })
967
+ ] });
968
+ }
969
+ function IssueReportModalDescription({
970
+ copy,
971
+ mode,
972
+ target
973
+ }) {
974
+ if (mode === "create" && target) {
975
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
976
+ copy.createDescriptionPrefix,
977
+ " ",
978
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { className: "rounded bg-slate-100 px-1.5 py-0.5 text-xs text-slate-700", children: target.page_url })
979
+ ] });
980
+ }
981
+ return mode === "edit" ? copy.editDescription : copy.replyDescription;
982
+ }
983
+ function IssueReportModalBody({
984
+ copy,
985
+ mode,
986
+ issue,
987
+ isHydrating,
988
+ error,
989
+ canUseVoice,
990
+ canUseText,
991
+ effectiveInputMode,
992
+ note,
993
+ normalizedNote,
994
+ isValid,
995
+ isSubmitting,
996
+ isVoiceActive,
997
+ isVoiceConnecting,
998
+ voice,
999
+ voiceTokenResult,
1000
+ committedTranscript,
1001
+ voiceError,
1002
+ scribeError,
1003
+ submitError,
1004
+ onRetryHydration,
1005
+ onClose,
1006
+ onSelectText,
1007
+ onSelectVoice,
1008
+ onStartVoiceInput,
1009
+ onStopVoiceInput,
1010
+ onAppendTranscript,
1011
+ onNoteChange,
1012
+ onSubmit
1013
+ }) {
1014
+ if (isHydrating) {
1015
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-sm text-slate-600", children: [
1016
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Spinner, { className: "h-4 w-4 animate-spin" }),
1017
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copy.hydrateLoading })
1018
+ ] });
1019
+ }
1020
+ if (error) {
1021
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
1022
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: error }),
1023
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2", children: [
1024
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1025
+ "button",
1026
+ {
1027
+ type: "button",
1028
+ className: "rounded-full border border-rose-300 px-3 py-1 font-medium transition hover:bg-rose-100",
1029
+ onClick: onRetryHydration,
1030
+ children: copy.retryAction
1031
+ }
1032
+ ),
1033
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1034
+ "button",
1035
+ {
1036
+ type: "button",
1037
+ className: "rounded-full border border-slate-300 px-3 py-1 font-medium text-slate-700 transition hover:bg-slate-50",
1038
+ onClick: onClose,
1039
+ children: copy.cancelAction
1040
+ }
1041
+ )
1042
+ ] })
1043
+ ] });
1044
+ }
1045
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1046
+ mode === "create" && canUseVoice ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1047
+ IssueReportVoicePanel,
1048
+ {
1049
+ canUseText,
1050
+ effectiveInputMode,
1051
+ isSubmitting,
1052
+ isVoiceActive,
1053
+ isVoiceConnecting,
1054
+ voice,
1055
+ voiceTokenResult,
1056
+ committedTranscript,
1057
+ voiceError,
1058
+ scribeError,
1059
+ onSelectText,
1060
+ onSelectVoice,
1061
+ onStartVoiceInput,
1062
+ onStopVoiceInput,
1063
+ onAppendTranscript
1064
+ }
1065
+ ) : null,
1066
+ mode === "reply" && issue ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: [
1067
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: copy.originalIssueLabel }),
1068
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-1 text-sm text-slate-700", children: issue.note })
1069
+ ] }) : null,
1070
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1071
+ IssueReportNoteEditor,
1072
+ {
1073
+ canUseText,
1074
+ note,
1075
+ normalizedNote,
1076
+ isSubmitting,
1077
+ copy,
1078
+ onNoteChange,
1079
+ onSubmit
1080
+ }
1081
+ ),
1082
+ submitError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-4 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700", children: submitError }) : null,
1083
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-6 flex justify-end gap-3", children: [
1084
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1085
+ "button",
1086
+ {
1087
+ type: "button",
1088
+ className: "rounded-full border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50",
1089
+ onClick: onClose,
1090
+ disabled: isSubmitting,
1091
+ children: copy.cancelAction
1092
+ }
1093
+ ),
1094
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1095
+ "button",
1096
+ {
1097
+ type: "button",
1098
+ className: cn(
1099
+ "rounded-full px-4 py-2 text-sm font-semibold text-white transition",
1100
+ isValid && !isSubmitting ? "bg-slate-900 hover:bg-slate-800" : "cursor-not-allowed bg-slate-300"
1101
+ ),
1102
+ onClick: onSubmit,
1103
+ disabled: !isValid || isSubmitting,
1104
+ children: isSubmitting ? copy.submittingAction : copy.submitAction
1105
+ }
1106
+ )
1107
+ ] })
1108
+ ] });
1109
+ }
1110
+ function useIssueReportVoiceCapture({
1111
+ canUseVoice,
1112
+ defaultInputMode,
1113
+ isSubmitting,
1114
+ voice
1115
+ }) {
1116
+ const [inputMode, setInputMode] = (0, import_react5.useState)(defaultInputMode);
1117
+ const [voiceTokenResult, setVoiceTokenResult] = (0, import_react5.useState)(null);
1118
+ const [voiceSubmitMetadata, setVoiceSubmitMetadata] = (0, import_react5.useState)(null);
1119
+ const [voiceError, setVoiceError] = (0, import_react5.useState)(null);
1120
+ const {
1121
+ status: scribeStatus,
1122
+ isConnected,
1123
+ isTranscribing,
1124
+ committedTranscripts,
1125
+ error: scribeError,
1126
+ connect: connectScribe,
1127
+ disconnect: disconnectScribe
1128
+ } = (0, import_react3.useScribe)({
1129
+ autoConnect: false,
1130
+ modelId: voice.modelId,
1131
+ microphone: voice.microphone
1132
+ });
1133
+ const committedTranscript = (0, import_react5.useMemo)(
1134
+ () => getCommittedTranscriptText(committedTranscripts),
1135
+ [committedTranscripts]
1136
+ );
1137
+ const isVoiceConnecting = scribeStatus === "connecting";
1138
+ const isVoiceActive = isConnected || isTranscribing;
1139
+ const resetVoiceCapture = (0, import_react5.useCallback)(() => {
1140
+ disconnectScribe();
1141
+ setInputMode(defaultInputMode);
1142
+ setVoiceTokenResult(null);
1143
+ setVoiceSubmitMetadata(null);
1144
+ setVoiceError(null);
1145
+ }, [defaultInputMode, disconnectScribe]);
1146
+ const startVoiceInput = (0, import_react5.useCallback)(
1147
+ async (createVoiceToken) => {
1148
+ if (!canUseVoice || isSubmitting || isVoiceActive || isVoiceConnecting) {
1149
+ return;
1150
+ }
1151
+ if (!createVoiceToken) {
1152
+ setVoiceError("Voice input is unavailable for this client.");
1153
+ return;
1154
+ }
1155
+ setVoiceError(null);
1156
+ try {
1157
+ const tokenResult = await createVoiceToken();
1158
+ setVoiceTokenResult(tokenResult);
1159
+ setVoiceSubmitMetadata(tokenResult);
1160
+ await connectScribe({
1161
+ token: tokenResult.token,
1162
+ modelId: tokenResult.model_id,
1163
+ microphone: voice.microphone
1164
+ });
1165
+ setInputMode("voice");
1166
+ } catch (startError) {
1167
+ setVoiceError(resolveErrorMessage(startError, "Failed to start voice input."));
1168
+ }
1169
+ },
1170
+ [
1171
+ canUseVoice,
1172
+ connectScribe,
1173
+ isSubmitting,
1174
+ isVoiceActive,
1175
+ isVoiceConnecting,
1176
+ voice.microphone
1177
+ ]
1178
+ );
1179
+ const appendTranscript = (0, import_react5.useCallback)(
1180
+ (setNote) => {
1181
+ if (!committedTranscript.trim()) {
1182
+ return;
1183
+ }
1184
+ setNote((current) => appendTranscriptToNote(current, committedTranscript));
1185
+ setInputMode("text");
1186
+ },
1187
+ [committedTranscript]
1188
+ );
1189
+ return {
1190
+ inputMode,
1191
+ setInputMode,
1192
+ voiceTokenResult,
1193
+ voiceSubmitMetadata,
1194
+ committedTranscript,
1195
+ voiceError,
1196
+ scribeError,
1197
+ isVoiceConnecting,
1198
+ isVoiceActive,
1199
+ resetVoiceCapture,
1200
+ startVoiceInput,
1201
+ stopVoiceInput: disconnectScribe,
1202
+ appendTranscript
1203
+ };
1204
+ }
757
1205
  function IssueReportModeBanner() {
758
1206
  const { copy } = useIssueReporting();
759
1207
  const reportMode = useReportMode();
760
1208
  if (!reportMode?.isReportMode) {
761
1209
  return null;
762
1210
  }
763
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "fixed inset-x-4 top-4 z-[70] flex justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "max-w-xl rounded-full border border-amber-300 bg-amber-50/95 px-4 py-3 text-sm text-amber-950 shadow-lg backdrop-blur", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-wrap items-center justify-center gap-3", children: [
1211
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("fixed inset-x-4 top-4 flex justify-center", Z_BANNER), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "max-w-xl rounded-full border border-amber-300 bg-amber-50/95 px-4 py-3 text-sm text-amber-950 shadow-lg backdrop-blur", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-wrap items-center justify-center gap-3", children: [
764
1212
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "font-medium", children: copy.reportModeTitle }),
765
1213
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-amber-900/80", children: copy.reportModeDescription }),
766
1214
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -783,7 +1231,7 @@ function IssueList({
783
1231
  const history = useIssueReportingHistory(filter);
784
1232
  if (history.isPending) {
785
1233
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-5 text-sm text-slate-600", children: [
786
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react3.Spinner, { className: "h-4 w-4 animate-spin" }),
1234
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Spinner, { className: "h-4 w-4 animate-spin" }),
787
1235
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copy.historyLoading })
788
1236
  ] });
789
1237
  }
@@ -813,7 +1261,7 @@ function IssueList({
813
1261
  {
814
1262
  className: "rounded-2xl border border-slate-200 bg-white px-3 py-3 shadow-sm",
815
1263
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-start gap-3", children: [
816
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-0.5 flex-shrink-0", children: isClosedIssueStatus(issue.status) ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react3.CheckCircle, { className: "h-4 w-4 text-emerald-600", weight: "fill" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react3.Circle, { className: "h-4 w-4 text-rose-600", weight: "fill" }) }),
1264
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-0.5 flex-shrink-0", children: isClosedIssueStatus(issue.status) ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.CheckCircle, { className: "h-4 w-4 text-emerald-600", weight: "fill" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Circle, { className: "h-4 w-4 text-rose-600", weight: "fill" }) }),
817
1265
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "min-w-0 flex-1", children: [
818
1266
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [
819
1267
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "min-w-0", children: [
@@ -823,13 +1271,14 @@ function IssueList({
823
1271
  "span",
824
1272
  {
825
1273
  className: cn(
826
- "rounded-full px-2 py-0.5 text-[11px] font-medium",
1274
+ "rounded-full px-2 py-0.5 font-medium",
1275
+ BADGE_TEXT,
827
1276
  getIssueStatusClassName(issue.status)
828
1277
  ),
829
1278
  children: getIssueStatusBadgeLabel(issue.status)
830
1279
  }
831
1280
  ),
832
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-medium text-slate-600", children: getIssueOriginText(issue, copy) }),
1281
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: cn("rounded-full bg-slate-100 px-2 py-0.5 font-medium text-slate-600", BADGE_TEXT), children: getIssueOriginText(issue, copy) }),
833
1282
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-xs text-slate-400", children: formatRelativeTime(issue.updated_at) })
834
1283
  ] })
835
1284
  ] }),
@@ -853,7 +1302,7 @@ function IssueList({
853
1302
  }) });
854
1303
  }
855
1304
  function IssueReportPopover({ children }) {
856
- const [filter, setFilter] = (0, import_react4.useState)("all");
1305
+ const [filter, setFilter] = (0, import_react5.useState)("all");
857
1306
  const {
858
1307
  copy,
859
1308
  isPopoverOpen,
@@ -871,7 +1320,7 @@ function IssueReportPopover({ children }) {
871
1320
  const history = useIssueReportingHistory("all");
872
1321
  const status = useIssueReportingStatus();
873
1322
  const allItems = history.data?.items ?? [];
874
- const counts = (0, import_react4.useMemo)(
1323
+ const counts = (0, import_react5.useMemo)(
875
1324
  () => ({
876
1325
  all: allItems.length,
877
1326
  unresolved: filterIssueReports(allItems, "unresolved").length,
@@ -896,7 +1345,12 @@ function IssueReportPopover({ children }) {
896
1345
  align: "end",
897
1346
  side: "top",
898
1347
  sideOffset: 12,
899
- className: "z-[70] w-[360px] rounded-3xl border border-slate-200 bg-white p-4 shadow-[0_18px_48px_rgba(15,23,42,0.18)]",
1348
+ className: cn(
1349
+ "rounded-3xl border border-slate-200 bg-white p-4",
1350
+ Z_POPOVER,
1351
+ POPOVER_WIDTH,
1352
+ POPOVER_SHADOW
1353
+ ),
900
1354
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-4", children: [
901
1355
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between gap-3", children: [
902
1356
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
@@ -976,7 +1430,7 @@ function IssueReportPopover({ children }) {
976
1430
  )
977
1431
  ] }) : null,
978
1432
  allowTenantScope ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-2", children: [
979
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-[11px] font-medium uppercase tracking-wide text-slate-500", children: copy.scopeLabel }),
1433
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("font-medium uppercase tracking-wide text-slate-500", LABEL_TEXT), children: copy.scopeLabel }),
980
1434
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex gap-2", children: [
981
1435
  ["tenant", copy.scopeTenant],
982
1436
  ["mine", copy.scopeMine]
@@ -1032,18 +1486,46 @@ function IssueReportPopover({ children }) {
1032
1486
  }
1033
1487
  function IssueReportModal() {
1034
1488
  const {
1489
+ client,
1035
1490
  copy,
1036
1491
  reporterRoleHint,
1037
1492
  modalState,
1038
1493
  closeModal,
1039
- retryModalHydration
1494
+ retryModalHydration,
1495
+ inputModes,
1496
+ defaultInputMode,
1497
+ voice
1040
1498
  } = useIssueReporting();
1041
1499
  const { createMutation, updateMutation, replyMutation } = useIssueReportingMutations();
1042
- const [note, setNote] = (0, import_react4.useState)("");
1043
- const [submitError, setSubmitError] = (0, import_react4.useState)(null);
1500
+ const [note, setNote] = (0, import_react5.useState)("");
1501
+ const [submitError, setSubmitError] = (0, import_react5.useState)(null);
1044
1502
  const { isOpen, mode, issue, target, isHydrating, error } = modalState;
1045
- (0, import_react4.useEffect)(() => {
1503
+ const canUseVoice = mode === "create" && inputModes.includes("voice");
1504
+ const canUseText = mode !== "create" || inputModes.includes("text");
1505
+ const isSubmitting = createMutation.isPending || updateMutation.isPending || replyMutation.isPending;
1506
+ const {
1507
+ inputMode,
1508
+ setInputMode,
1509
+ voiceTokenResult,
1510
+ voiceSubmitMetadata,
1511
+ committedTranscript,
1512
+ voiceError,
1513
+ scribeError,
1514
+ isVoiceConnecting,
1515
+ isVoiceActive,
1516
+ resetVoiceCapture,
1517
+ startVoiceInput,
1518
+ stopVoiceInput,
1519
+ appendTranscript
1520
+ } = useIssueReportVoiceCapture({
1521
+ canUseVoice,
1522
+ defaultInputMode,
1523
+ isSubmitting,
1524
+ voice
1525
+ });
1526
+ (0, import_react5.useEffect)(() => {
1046
1527
  if (!isOpen) {
1528
+ resetVoiceCapture();
1047
1529
  setNote("");
1048
1530
  setSubmitError(null);
1049
1531
  return;
@@ -1053,140 +1535,145 @@ function IssueReportModal() {
1053
1535
  } else {
1054
1536
  setNote("");
1055
1537
  }
1538
+ resetVoiceCapture();
1056
1539
  setSubmitError(null);
1057
- }, [isOpen, mode, issue]);
1058
- const isValid = note.trim().length >= 10 && note.trim().length <= 2e3;
1059
- const isSubmitting = createMutation.isPending || updateMutation.isPending || replyMutation.isPending;
1540
+ }, [isOpen, issue, mode, resetVoiceCapture]);
1541
+ const effectiveInputMode = mode !== "create" ? "text" : canUseVoice && inputMode === "voice" ? "voice" : "text";
1542
+ const normalizedNote = effectiveInputMode === "voice" ? committedTranscript.trim() : note.trim();
1543
+ const isValid = normalizedNote.length >= 10 && normalizedNote.length <= 2e3;
1060
1544
  const title = mode === "create" ? `${copy.createTitlePrefix}: ${target?.component_label ?? ""}` : mode === "edit" ? `${copy.editTitlePrefix}: ${target?.component_label ?? ""}` : `${copy.replyTitlePrefix}: ${target?.component_label ?? ""}`;
1545
+ const handleCloseModal = () => {
1546
+ resetVoiceCapture();
1547
+ closeModal();
1548
+ };
1549
+ const handleStartVoiceInput = async () => {
1550
+ await startVoiceInput(client.issueReporting.createVoiceToken);
1551
+ };
1552
+ const handleStopVoiceInput = () => {
1553
+ stopVoiceInput();
1554
+ };
1555
+ const handleAppendTranscript = () => {
1556
+ appendTranscript(setNote);
1557
+ };
1061
1558
  const handleSubmit = async () => {
1062
1559
  if (!target || !isValid || isSubmitting) {
1063
1560
  return;
1064
1561
  }
1065
1562
  setSubmitError(null);
1066
1563
  try {
1067
- const normalizedNote = note.trim();
1564
+ const noteForSubmit = normalizedNote;
1565
+ const effectiveVoiceMetadata = effectiveInputMode === "voice" ? voiceSubmitMetadata ?? {
1566
+ provider: voice.provider,
1567
+ token: "",
1568
+ model_id: voice.modelId,
1569
+ expires_in_seconds: 0,
1570
+ retain_audio: false,
1571
+ attach_transcript: true
1572
+ } : null;
1068
1573
  if (mode === "create") {
1069
1574
  await createMutation.mutateAsync({
1070
- target,
1071
- note: normalizedNote,
1575
+ target: effectiveVoiceMetadata && effectiveInputMode === "voice" ? {
1576
+ ...target,
1577
+ metadata: {
1578
+ ...target.metadata ?? {},
1579
+ capture: {
1580
+ input_mode: "voice",
1581
+ provider: effectiveVoiceMetadata.provider,
1582
+ model_id: effectiveVoiceMetadata.model_id,
1583
+ retain_audio: effectiveVoiceMetadata.retain_audio,
1584
+ attach_transcript: effectiveVoiceMetadata.attach_transcript
1585
+ }
1586
+ }
1587
+ } : target,
1588
+ note: noteForSubmit,
1072
1589
  reporter_role_hint: reporterRoleHint
1073
1590
  });
1074
1591
  } else if (mode === "edit" && issue) {
1075
1592
  await updateMutation.mutateAsync({
1076
1593
  issueReportId: issue.id,
1077
- note: normalizedNote
1594
+ note: noteForSubmit
1078
1595
  });
1079
1596
  } else if (mode === "reply" && issue) {
1080
1597
  await replyMutation.mutateAsync({
1081
1598
  issueReportId: issue.id,
1082
- note: normalizedNote,
1599
+ note: noteForSubmit,
1083
1600
  reporterRoleHint
1084
1601
  });
1085
1602
  }
1603
+ resetVoiceCapture();
1086
1604
  closeModal();
1087
1605
  } catch (submissionError) {
1088
1606
  setSubmitError(resolveErrorMessage(submissionError, "Failed to submit issue report"));
1089
1607
  }
1090
1608
  };
1091
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Root, { open: isOpen, onOpenChange: (open) => !open && closeModal(), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Dialog.Portal, { children: [
1092
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Overlay, { className: "fixed inset-0 z-[80] bg-slate-950/45 backdrop-blur-sm" }),
1093
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Dialog.Content, { className: "fixed left-1/2 top-1/2 z-[81] w-[calc(100vw-2rem)] max-w-xl -translate-x-1/2 -translate-y-1/2 rounded-[28px] border border-slate-200 bg-white p-6 shadow-[0_28px_80px_rgba(15,23,42,0.24)] focus:outline-none", children: [
1094
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Title, { className: "text-lg font-semibold text-slate-950", children: title }),
1095
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Description, { className: "mt-2 text-sm text-slate-600", children: mode === "create" && target ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1096
- copy.createDescriptionPrefix,
1097
- " ",
1098
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { className: "rounded bg-slate-100 px-1.5 py-0.5 text-xs text-slate-700", children: target.page_url })
1099
- ] }) : mode === "edit" ? copy.editDescription : copy.replyDescription }),
1100
- isHydrating ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-sm text-slate-600", children: [
1101
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react3.Spinner, { className: "h-4 w-4 animate-spin" }),
1102
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copy.hydrateLoading })
1103
- ] }) : error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
1104
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: error }),
1105
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2", children: [
1106
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1107
- "button",
1108
- {
1109
- type: "button",
1110
- className: "rounded-full border border-rose-300 px-3 py-1 font-medium transition hover:bg-rose-100",
1111
- onClick: () => retryModalHydration(),
1112
- children: copy.retryAction
1113
- }
1114
- ),
1115
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1116
- "button",
1117
- {
1118
- type: "button",
1119
- className: "rounded-full border border-slate-300 px-3 py-1 font-medium text-slate-700 transition hover:bg-slate-50",
1120
- onClick: closeModal,
1121
- children: copy.cancelAction
1122
- }
1123
- )
1124
- ] })
1125
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1126
- mode === "reply" && issue ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: [
1127
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: copy.originalIssueLabel }),
1128
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-1 text-sm text-slate-700", children: issue.note })
1129
- ] }) : null,
1130
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 space-y-2", children: [
1131
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1132
- "textarea",
1609
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Root, { open: isOpen, onOpenChange: (open) => !open && handleCloseModal(), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Dialog.Portal, { children: [
1610
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Overlay, { className: cn("fixed inset-0 bg-slate-950/45 backdrop-blur-sm", Z_MODAL_OVERLAY) }),
1611
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1612
+ Dialog.Content,
1613
+ {
1614
+ className: cn(
1615
+ "fixed left-1/2 top-1/2 max-w-xl -translate-x-1/2 -translate-y-1/2 border border-slate-200 bg-white p-6 focus:outline-none",
1616
+ Z_MODAL_CONTENT,
1617
+ MODAL_WIDTH,
1618
+ MODAL_RADIUS,
1619
+ MODAL_SHADOW
1620
+ ),
1621
+ children: [
1622
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Title, { className: "text-lg font-semibold text-slate-950", children: title }),
1623
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Description, { className: "mt-2 text-sm text-slate-600", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1624
+ IssueReportModalDescription,
1133
1625
  {
1134
- value: note,
1135
- onChange: (event) => setNote(event.target.value),
1136
- onKeyDown: (event) => {
1137
- if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
1138
- event.preventDefault();
1139
- void handleSubmit();
1140
- }
1141
- },
1142
- placeholder: copy.notePlaceholder,
1143
- className: "h-36 w-full resize-none rounded-2xl border border-slate-300 px-4 py-3 text-sm text-slate-800 outline-none transition focus:border-slate-500 focus:ring-2 focus:ring-slate-200",
1144
- disabled: isSubmitting,
1145
- autoFocus: true
1626
+ copy,
1627
+ mode,
1628
+ target
1146
1629
  }
1147
- ),
1148
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between text-xs text-slate-500", children: [
1149
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: getIssueNoteLengthMessage(note, copy) }),
1150
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copy.keyboardShortcutHint })
1151
- ] })
1152
- ] }),
1153
- submitError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-4 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700", children: submitError }) : null,
1154
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-6 flex justify-end gap-3", children: [
1630
+ ) }),
1155
1631
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1156
- "button",
1632
+ IssueReportModalBody,
1157
1633
  {
1158
- type: "button",
1159
- className: "rounded-full border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50",
1160
- onClick: closeModal,
1161
- disabled: isSubmitting,
1162
- children: copy.cancelAction
1634
+ copy,
1635
+ mode,
1636
+ issue,
1637
+ isHydrating,
1638
+ error,
1639
+ canUseVoice,
1640
+ canUseText,
1641
+ effectiveInputMode,
1642
+ note,
1643
+ normalizedNote,
1644
+ isValid,
1645
+ isSubmitting,
1646
+ isVoiceActive,
1647
+ isVoiceConnecting,
1648
+ voice,
1649
+ voiceTokenResult,
1650
+ committedTranscript,
1651
+ voiceError,
1652
+ scribeError,
1653
+ submitError,
1654
+ onRetryHydration: () => void retryModalHydration(),
1655
+ onClose: handleCloseModal,
1656
+ onSelectText: () => setInputMode("text"),
1657
+ onSelectVoice: () => setInputMode("voice"),
1658
+ onStartVoiceInput: () => void handleStartVoiceInput(),
1659
+ onStopVoiceInput: handleStopVoiceInput,
1660
+ onAppendTranscript: handleAppendTranscript,
1661
+ onNoteChange: setNote,
1662
+ onSubmit: () => void handleSubmit()
1163
1663
  }
1164
1664
  ),
1165
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1665
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1166
1666
  "button",
1167
1667
  {
1168
1668
  type: "button",
1169
- className: cn(
1170
- "rounded-full px-4 py-2 text-sm font-semibold text-white transition",
1171
- isValid && !isSubmitting ? "bg-slate-900 hover:bg-slate-800" : "cursor-not-allowed bg-slate-300"
1172
- ),
1173
- onClick: () => void handleSubmit(),
1174
- disabled: !isValid || isSubmitting,
1175
- children: isSubmitting ? copy.submittingAction : copy.submitAction
1669
+ className: "absolute right-4 top-4 inline-flex h-9 w-9 items-center justify-center rounded-full border border-slate-200 text-slate-500 transition hover:bg-slate-50 hover:text-slate-900",
1670
+ "aria-label": "Close issue report modal",
1671
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.X, { className: "h-4 w-4" })
1176
1672
  }
1177
- )
1178
- ] })
1179
- ] }),
1180
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1181
- "button",
1182
- {
1183
- type: "button",
1184
- className: "absolute right-4 top-4 inline-flex h-9 w-9 items-center justify-center rounded-full border border-slate-200 text-slate-500 transition hover:bg-slate-50 hover:text-slate-900",
1185
- "aria-label": "Close issue report modal",
1186
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react3.X, { className: "h-4 w-4" })
1187
- }
1188
- ) })
1189
- ] })
1673
+ ) })
1674
+ ]
1675
+ }
1676
+ )
1190
1677
  ] }) });
1191
1678
  }
1192
1679
  function FloatingIssueReportButton({
@@ -1208,7 +1695,7 @@ function FloatingIssueReportButton({
1208
1695
  }
1209
1696
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1210
1697
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IssueReportModeBanner, {}),
1211
- !isReportMode ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("fixed bottom-12 right-4 z-[65]", positionClassName), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IssueReportPopover, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1698
+ !isReportMode ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("fixed bottom-12 right-4", Z_FLOATING_BUTTON, positionClassName), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IssueReportPopover, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1212
1699
  "button",
1213
1700
  {
1214
1701
  type: "button",
@@ -1220,7 +1707,7 @@ function FloatingIssueReportButton({
1220
1707
  className
1221
1708
  ),
1222
1709
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1223
- import_react3.BugBeetle,
1710
+ import_react4.BugBeetle,
1224
1711
  {
1225
1712
  className: cn("h-6 w-6", getEntryPointClassName(entryPointState)),
1226
1713
  weight: "fill"
@@ -1239,9 +1726,9 @@ function ReportableSection({
1239
1726
  }) {
1240
1727
  const reportMode = useReportMode();
1241
1728
  const isSelectable = Boolean(reportMode?.isReportMode);
1242
- const targetId = import_react4.default.useRef(/* @__PURE__ */ Symbol("reportable-section"));
1243
- const elementRef = import_react4.default.useRef(null);
1244
- (0, import_react4.useEffect)(() => {
1729
+ const targetId = import_react5.default.useRef(/* @__PURE__ */ Symbol("reportable-section"));
1730
+ const elementRef = import_react5.default.useRef(null);
1731
+ (0, import_react5.useEffect)(() => {
1245
1732
  if (!reportMode) {
1246
1733
  return;
1247
1734
  }
@@ -1250,7 +1737,7 @@ function ReportableSection({
1250
1737
  reportMode.unregisterTarget(targetId.current);
1251
1738
  };
1252
1739
  }, [reportMode, reportableName]);
1253
- return import_react4.default.createElement(
1740
+ return import_react5.default.createElement(
1254
1741
  Component,
1255
1742
  {
1256
1743
  ref: (node) => {