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/CHANGELOG.md +8 -0
- package/README.md +61 -5
- package/dist/index.d.mts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +606 -119
- package/dist/index.mjs +595 -106
- package/package.json +6 -3
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("@
|
|
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
|
|
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
|
|
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)(
|
|
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)(
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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:
|
|
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: "
|
|
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,
|
|
1043
|
-
const [submitError, setSubmitError] = (0,
|
|
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
|
-
|
|
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,
|
|
1058
|
-
const
|
|
1059
|
-
const
|
|
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
|
|
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
|
-
|
|
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:
|
|
1594
|
+
note: noteForSubmit
|
|
1078
1595
|
});
|
|
1079
1596
|
} else if (mode === "reply" && issue) {
|
|
1080
1597
|
await replyMutation.mutateAsync({
|
|
1081
1598
|
issueReportId: issue.id,
|
|
1082
|
-
note:
|
|
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 &&
|
|
1092
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Overlay, { className: "fixed inset-0
|
|
1093
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
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
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
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
|
-
|
|
1632
|
+
IssueReportModalBody,
|
|
1157
1633
|
{
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
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:
|
|
1170
|
-
|
|
1171
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
1243
|
-
const elementRef =
|
|
1244
|
-
(0,
|
|
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
|
|
1740
|
+
return import_react5.default.createElement(
|
|
1254
1741
|
Component,
|
|
1255
1742
|
{
|
|
1256
1743
|
ref: (node) => {
|