spaps-issue-reporting-react 0.1.2 → 0.1.4
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 +87 -7
- package/dist/index.d.mts +44 -4
- package/dist/index.d.ts +44 -4
- package/dist/index.js +902 -147
- package/dist/index.mjs +895 -138
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
FloatingIssueReportButton: () => FloatingIssueReportButton,
|
|
34
|
+
IssueReportingPageConfig: () => IssueReportingPageConfig,
|
|
34
35
|
IssueReportingProvider: () => IssueReportingProvider,
|
|
35
36
|
ReportModeContext: () => ReportModeContext,
|
|
36
37
|
ReportableSection: () => ReportableSection,
|
|
@@ -55,9 +56,10 @@ module.exports = __toCommonJS(index_exports);
|
|
|
55
56
|
// src/components.tsx
|
|
56
57
|
var Dialog = __toESM(require("@radix-ui/react-dialog"));
|
|
57
58
|
var Popover = __toESM(require("@radix-ui/react-popover"));
|
|
58
|
-
var import_react3 = require("@
|
|
59
|
+
var import_react3 = require("@elevenlabs/react");
|
|
60
|
+
var import_react4 = require("@phosphor-icons/react");
|
|
59
61
|
var import_date_fns = require("date-fns");
|
|
60
|
-
var
|
|
62
|
+
var import_react5 = __toESM(require("react"));
|
|
61
63
|
|
|
62
64
|
// src/provider.tsx
|
|
63
65
|
var import_react2 = require("react");
|
|
@@ -77,6 +79,9 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
77
79
|
var LIST_LIMIT = 200;
|
|
78
80
|
var NOTE_MIN_LENGTH = 10;
|
|
79
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";
|
|
80
85
|
var initialModalState = {
|
|
81
86
|
isOpen: false,
|
|
82
87
|
mode: "create",
|
|
@@ -89,10 +94,24 @@ var initialModalState = {
|
|
|
89
94
|
var IssueReportingContext = (0, import_react2.createContext)(
|
|
90
95
|
void 0
|
|
91
96
|
);
|
|
97
|
+
var IssueReportingPageConfigRegistryContext = (0, import_react2.createContext)(void 0);
|
|
98
|
+
var hiddenMarkerStyle = {
|
|
99
|
+
position: "absolute",
|
|
100
|
+
width: "1px",
|
|
101
|
+
height: "1px",
|
|
102
|
+
padding: 0,
|
|
103
|
+
margin: "-1px",
|
|
104
|
+
overflow: "hidden",
|
|
105
|
+
clip: "rect(0, 0, 0, 0)",
|
|
106
|
+
whiteSpace: "nowrap",
|
|
107
|
+
border: 0
|
|
108
|
+
};
|
|
92
109
|
var defaultIssueReportingCopy = {
|
|
93
110
|
entryAriaLabel: "Report issue",
|
|
94
111
|
popoverTitle: "Issue Reports",
|
|
95
112
|
reportNewAction: "Report New Issue",
|
|
113
|
+
reportPageAction: "Report This Page",
|
|
114
|
+
reportSpecificAction: "Pick Specific Section",
|
|
96
115
|
filtersAll: "All",
|
|
97
116
|
filtersUnresolved: "Unresolved",
|
|
98
117
|
filtersResolved: "Resolved",
|
|
@@ -109,6 +128,9 @@ var defaultIssueReportingCopy = {
|
|
|
109
128
|
reportModeTitle: "Report mode is active",
|
|
110
129
|
reportModeDescription: "Click a highlighted section to capture the broken surface.",
|
|
111
130
|
reportModeCancelAction: "Cancel",
|
|
131
|
+
generalPageTargetLabel: "Current Page",
|
|
132
|
+
surfaceRequiredDescription: "This page requires selecting a specific section before you submit a report.",
|
|
133
|
+
specificSectionUnavailableDescription: "No specific sections are registered on this page right now.",
|
|
112
134
|
createTitlePrefix: "Report Issue",
|
|
113
135
|
editTitlePrefix: "Edit Issue",
|
|
114
136
|
replyTitlePrefix: "Reply to",
|
|
@@ -152,6 +174,25 @@ function resolveInitialScope(defaultScope, allowTenantScope) {
|
|
|
152
174
|
}
|
|
153
175
|
return "mine";
|
|
154
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
|
+
}
|
|
155
196
|
function normalizeTarget(target, getPageUrl) {
|
|
156
197
|
if (typeof target === "string") {
|
|
157
198
|
const normalized = target.trim();
|
|
@@ -173,6 +214,44 @@ function normalizeTarget(target, getPageUrl) {
|
|
|
173
214
|
metadata: target.metadata ?? {}
|
|
174
215
|
};
|
|
175
216
|
}
|
|
217
|
+
function summarizeTarget(target) {
|
|
218
|
+
if (typeof target === "string") {
|
|
219
|
+
const normalized = target.trim();
|
|
220
|
+
return {
|
|
221
|
+
component_key: normalized,
|
|
222
|
+
component_label: normalized,
|
|
223
|
+
surface_ref: null,
|
|
224
|
+
metadata: {}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const key = target.componentKey.trim();
|
|
228
|
+
return {
|
|
229
|
+
component_key: key,
|
|
230
|
+
component_label: (target.componentLabel ?? key).trim(),
|
|
231
|
+
surface_ref: target.surfaceRef ?? null,
|
|
232
|
+
metadata: target.metadata ?? {}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function isElementVisibleForReporting(element) {
|
|
236
|
+
if (!element || typeof window === "undefined") {
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
let current = element;
|
|
240
|
+
while (current) {
|
|
241
|
+
if (current.getAttribute("aria-hidden") === "true") {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
if (current instanceof HTMLElement && (current.hidden || current.inert)) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
const computedStyle = window.getComputedStyle(current);
|
|
248
|
+
if (computedStyle.display === "none" || computedStyle.visibility === "hidden" || computedStyle.visibility === "collapse") {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
current = current.parentElement;
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
176
255
|
function normalizeIssueTarget(issue) {
|
|
177
256
|
return {
|
|
178
257
|
component_key: issue.target.component_key,
|
|
@@ -254,6 +333,23 @@ function getIssueNoteLengthMessage(note, copy) {
|
|
|
254
333
|
}
|
|
255
334
|
return `${note.length}/${NOTE_MAX_LENGTH}`;
|
|
256
335
|
}
|
|
336
|
+
function buildGeneralPageTarget(copy, registeredTargets, getPageUrl) {
|
|
337
|
+
return {
|
|
338
|
+
component_key: "general_page",
|
|
339
|
+
component_label: copy.generalPageTargetLabel,
|
|
340
|
+
page_url: resolvePageUrl(getPageUrl),
|
|
341
|
+
surface_ref: null,
|
|
342
|
+
metadata: {
|
|
343
|
+
capture_mode: "general_page",
|
|
344
|
+
registered_target_count: registeredTargets.length,
|
|
345
|
+
...registeredTargets.length > 0 ? {
|
|
346
|
+
registered_targets: registeredTargets.map(
|
|
347
|
+
({ target }) => summarizeTarget(target)
|
|
348
|
+
)
|
|
349
|
+
} : {}
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
}
|
|
257
353
|
function useIssueReporting() {
|
|
258
354
|
const context = (0, import_react2.useContext)(IssueReportingContext);
|
|
259
355
|
if (!context) {
|
|
@@ -263,6 +359,25 @@ function useIssueReporting() {
|
|
|
263
359
|
}
|
|
264
360
|
return context;
|
|
265
361
|
}
|
|
362
|
+
function IssueReportingPageConfig({
|
|
363
|
+
createMode
|
|
364
|
+
}) {
|
|
365
|
+
const registry = (0, import_react2.useContext)(IssueReportingPageConfigRegistryContext);
|
|
366
|
+
const configId = (0, import_react2.useRef)(/* @__PURE__ */ Symbol("issue-reporting-page-config"));
|
|
367
|
+
const markerRef = (0, import_react2.useRef)(null);
|
|
368
|
+
if (!registry) {
|
|
369
|
+
throw new Error(
|
|
370
|
+
"IssueReportingPageConfig must be used within an IssueReportingProvider"
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
(0, import_react2.useEffect)(() => {
|
|
374
|
+
registry.registerPageConfig(configId.current, createMode, () => markerRef.current);
|
|
375
|
+
return () => {
|
|
376
|
+
registry.unregisterPageConfig(configId.current);
|
|
377
|
+
};
|
|
378
|
+
}, [createMode, registry]);
|
|
379
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ref: markerRef, style: hiddenMarkerStyle });
|
|
380
|
+
}
|
|
266
381
|
function useIssueReportingStatus() {
|
|
267
382
|
const { client, isEligible, scope } = useIssueReporting();
|
|
268
383
|
return (0, import_react_query.useQuery)({
|
|
@@ -335,6 +450,10 @@ function IssueReportingProvider({
|
|
|
335
450
|
getPageUrl,
|
|
336
451
|
defaultScope,
|
|
337
452
|
allowTenantScope = false,
|
|
453
|
+
defaultCreateMode = "general_page",
|
|
454
|
+
inputModes,
|
|
455
|
+
defaultInputMode,
|
|
456
|
+
voice,
|
|
338
457
|
copy,
|
|
339
458
|
children
|
|
340
459
|
}) {
|
|
@@ -352,6 +471,27 @@ function IssueReportingProvider({
|
|
|
352
471
|
const [scope, setScope] = (0, import_react2.useState)(
|
|
353
472
|
() => resolveInitialScope(defaultScope, allowTenantScope)
|
|
354
473
|
);
|
|
474
|
+
const [pageConfigs, setPageConfigs] = (0, import_react2.useState)([]);
|
|
475
|
+
const [registeredTargets, setRegisteredTargets] = (0, import_react2.useState)(
|
|
476
|
+
[]
|
|
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
|
+
);
|
|
355
495
|
(0, import_react2.useEffect)(() => {
|
|
356
496
|
setScope(resolveInitialScope(defaultScope, allowTenantScope));
|
|
357
497
|
}, [allowTenantScope, defaultScope]);
|
|
@@ -361,6 +501,51 @@ function IssueReportingProvider({
|
|
|
361
501
|
const openPopover = (0, import_react2.useCallback)(() => {
|
|
362
502
|
setIsPopoverOpen(true);
|
|
363
503
|
}, []);
|
|
504
|
+
const openCreateModal = (0, import_react2.useCallback)((target) => {
|
|
505
|
+
setIsPopoverOpen(false);
|
|
506
|
+
setIsReportMode(false);
|
|
507
|
+
setModalState({
|
|
508
|
+
isOpen: true,
|
|
509
|
+
mode: "create",
|
|
510
|
+
issueReportId: null,
|
|
511
|
+
issue: null,
|
|
512
|
+
target,
|
|
513
|
+
error: null,
|
|
514
|
+
isHydrating: false
|
|
515
|
+
});
|
|
516
|
+
}, []);
|
|
517
|
+
const registerPageConfig = (0, import_react2.useCallback)(
|
|
518
|
+
(id, createMode2, getElement) => {
|
|
519
|
+
setPageConfigs((current) => [
|
|
520
|
+
...current.filter((entry) => entry.id !== id),
|
|
521
|
+
{ id, createMode: createMode2, getElement }
|
|
522
|
+
]);
|
|
523
|
+
},
|
|
524
|
+
[]
|
|
525
|
+
);
|
|
526
|
+
const unregisterPageConfig = (0, import_react2.useCallback)((id) => {
|
|
527
|
+
setPageConfigs((current) => current.filter((entry) => entry.id !== id));
|
|
528
|
+
}, []);
|
|
529
|
+
const registerTarget = (0, import_react2.useCallback)(
|
|
530
|
+
(id, target, getElement) => {
|
|
531
|
+
setRegisteredTargets((current) => [
|
|
532
|
+
...current.filter((entry) => entry.id !== id),
|
|
533
|
+
{ id, target, getElement }
|
|
534
|
+
]);
|
|
535
|
+
},
|
|
536
|
+
[]
|
|
537
|
+
);
|
|
538
|
+
const unregisterTarget = (0, import_react2.useCallback)((id) => {
|
|
539
|
+
setRegisteredTargets((current) => current.filter((entry) => entry.id !== id));
|
|
540
|
+
}, []);
|
|
541
|
+
const visiblePageConfigs = pageConfigs.filter(
|
|
542
|
+
({ getElement }) => isElementVisibleForReporting(getElement())
|
|
543
|
+
);
|
|
544
|
+
const visibleRegisteredTargets = registeredTargets.filter(
|
|
545
|
+
({ getElement }) => isElementVisibleForReporting(getElement())
|
|
546
|
+
);
|
|
547
|
+
const createMode = visiblePageConfigs.length > 0 ? visiblePageConfigs[visiblePageConfigs.length - 1].createMode : defaultCreateMode;
|
|
548
|
+
const hasRegisteredTargets = visibleRegisteredTargets.length > 0;
|
|
364
549
|
const enterReportMode = (0, import_react2.useCallback)(() => {
|
|
365
550
|
setIsReportMode(true);
|
|
366
551
|
setIsPopoverOpen(false);
|
|
@@ -372,6 +557,42 @@ function IssueReportingProvider({
|
|
|
372
557
|
setModalState(initialModalState);
|
|
373
558
|
setIsPopoverOpen(true);
|
|
374
559
|
}, []);
|
|
560
|
+
const openPageIssueModal = (0, import_react2.useCallback)(() => {
|
|
561
|
+
if (!isEligible) {
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
openCreateModal(
|
|
565
|
+
buildGeneralPageTarget(mergedCopy, visibleRegisteredTargets, getPageUrl)
|
|
566
|
+
);
|
|
567
|
+
}, [
|
|
568
|
+
getPageUrl,
|
|
569
|
+
isEligible,
|
|
570
|
+
mergedCopy,
|
|
571
|
+
openCreateModal,
|
|
572
|
+
visibleRegisteredTargets
|
|
573
|
+
]);
|
|
574
|
+
const startNewIssue = (0, import_react2.useCallback)(() => {
|
|
575
|
+
if (!isEligible) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (createMode === "surface_required") {
|
|
579
|
+
if (hasRegisteredTargets) {
|
|
580
|
+
enterReportMode();
|
|
581
|
+
}
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (createMode === "surface_preferred" && hasRegisteredTargets) {
|
|
585
|
+
enterReportMode();
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
openPageIssueModal();
|
|
589
|
+
}, [
|
|
590
|
+
createMode,
|
|
591
|
+
enterReportMode,
|
|
592
|
+
hasRegisteredTargets,
|
|
593
|
+
isEligible,
|
|
594
|
+
openPageIssueModal
|
|
595
|
+
]);
|
|
375
596
|
const hydrateIssueIntoModal = (0, import_react2.useCallback)(
|
|
376
597
|
async (issueReportId, mode) => {
|
|
377
598
|
setModalState({
|
|
@@ -430,18 +651,9 @@ function IssueReportingProvider({
|
|
|
430
651
|
return;
|
|
431
652
|
}
|
|
432
653
|
const normalizedTarget = normalizeTarget(target, getPageUrl);
|
|
433
|
-
|
|
434
|
-
setModalState({
|
|
435
|
-
isOpen: true,
|
|
436
|
-
mode: "create",
|
|
437
|
-
issueReportId: null,
|
|
438
|
-
issue: null,
|
|
439
|
-
target: normalizedTarget,
|
|
440
|
-
error: null,
|
|
441
|
-
isHydrating: false
|
|
442
|
-
});
|
|
654
|
+
openCreateModal(normalizedTarget);
|
|
443
655
|
},
|
|
444
|
-
[getPageUrl, isEligible]
|
|
656
|
+
[getPageUrl, isEligible, openCreateModal]
|
|
445
657
|
);
|
|
446
658
|
const contextValue = (0, import_react2.useMemo)(
|
|
447
659
|
() => ({
|
|
@@ -451,9 +663,12 @@ function IssueReportingProvider({
|
|
|
451
663
|
principalId: principalId ?? null,
|
|
452
664
|
copy: mergedCopy,
|
|
453
665
|
isReportMode,
|
|
666
|
+
hasRegisteredTargets,
|
|
454
667
|
enterReportMode,
|
|
455
668
|
cancelReportMode,
|
|
456
669
|
selectPanel,
|
|
670
|
+
openPageIssueModal,
|
|
671
|
+
startNewIssue,
|
|
457
672
|
isPopoverOpen,
|
|
458
673
|
openPopover,
|
|
459
674
|
closePopover,
|
|
@@ -463,7 +678,11 @@ function IssueReportingProvider({
|
|
|
463
678
|
retryModalHydration,
|
|
464
679
|
scope,
|
|
465
680
|
setScope,
|
|
466
|
-
allowTenantScope
|
|
681
|
+
allowTenantScope,
|
|
682
|
+
createMode,
|
|
683
|
+
inputModes: resolvedInputModes,
|
|
684
|
+
defaultInputMode: resolvedDefaultInputMode,
|
|
685
|
+
voice: resolvedVoiceConfig
|
|
467
686
|
}),
|
|
468
687
|
[
|
|
469
688
|
allowTenantScope,
|
|
@@ -471,30 +690,60 @@ function IssueReportingProvider({
|
|
|
471
690
|
client,
|
|
472
691
|
closeModal,
|
|
473
692
|
closePopover,
|
|
693
|
+
createMode,
|
|
474
694
|
enterReportMode,
|
|
695
|
+
hasRegisteredTargets,
|
|
475
696
|
isEligible,
|
|
476
697
|
isPopoverOpen,
|
|
477
698
|
isReportMode,
|
|
478
699
|
mergedCopy,
|
|
479
700
|
modalState,
|
|
480
701
|
openExistingIssueModal,
|
|
702
|
+
openPageIssueModal,
|
|
481
703
|
openPopover,
|
|
482
704
|
principalId,
|
|
483
705
|
reporterRoleHint,
|
|
484
706
|
retryModalHydration,
|
|
707
|
+
resolvedDefaultInputMode,
|
|
708
|
+
resolvedInputModes,
|
|
709
|
+
resolvedVoiceConfig,
|
|
485
710
|
scope,
|
|
486
|
-
selectPanel
|
|
711
|
+
selectPanel,
|
|
712
|
+
startNewIssue
|
|
487
713
|
]
|
|
488
714
|
);
|
|
489
715
|
const reportModeValue = (0, import_react2.useMemo)(
|
|
490
716
|
() => ({
|
|
491
717
|
isReportMode,
|
|
492
718
|
selectPanel,
|
|
493
|
-
cancelReportMode
|
|
719
|
+
cancelReportMode,
|
|
720
|
+
hasRegisteredTargets,
|
|
721
|
+
registerTarget,
|
|
722
|
+
unregisterTarget
|
|
723
|
+
}),
|
|
724
|
+
[
|
|
725
|
+
cancelReportMode,
|
|
726
|
+
hasRegisteredTargets,
|
|
727
|
+
isReportMode,
|
|
728
|
+
registerTarget,
|
|
729
|
+
selectPanel,
|
|
730
|
+
unregisterTarget
|
|
731
|
+
]
|
|
732
|
+
);
|
|
733
|
+
const pageConfigRegistryValue = (0, import_react2.useMemo)(
|
|
734
|
+
() => ({
|
|
735
|
+
registerPageConfig,
|
|
736
|
+
unregisterPageConfig
|
|
494
737
|
}),
|
|
495
|
-
[
|
|
738
|
+
[registerPageConfig, unregisterPageConfig]
|
|
739
|
+
);
|
|
740
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
741
|
+
IssueReportingPageConfigRegistryContext.Provider,
|
|
742
|
+
{
|
|
743
|
+
value: pageConfigRegistryValue,
|
|
744
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReportModeContext.Provider, { value: reportModeValue, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IssueReportingContext.Provider, { value: contextValue, children }) })
|
|
745
|
+
}
|
|
496
746
|
);
|
|
497
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReportModeContext.Provider, { value: reportModeValue, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IssueReportingContext.Provider, { value: contextValue, children }) });
|
|
498
747
|
}
|
|
499
748
|
|
|
500
749
|
// src/components.tsx
|
|
@@ -502,6 +751,18 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
|
502
751
|
function cn(...values) {
|
|
503
752
|
return values.filter(Boolean).join(" ");
|
|
504
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]";
|
|
505
766
|
function truncate(value, max = 80) {
|
|
506
767
|
if (value.length <= max) {
|
|
507
768
|
return value;
|
|
@@ -522,6 +783,16 @@ function resolveErrorMessage(error, fallback) {
|
|
|
522
783
|
}
|
|
523
784
|
return fallback;
|
|
524
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
|
+
}
|
|
525
796
|
function resolveReporterId(issue) {
|
|
526
797
|
return issue.reporter_principal_id ?? issue.reporter_user_id ?? null;
|
|
527
798
|
}
|
|
@@ -554,13 +825,390 @@ function getIssueOriginText(issue, copy) {
|
|
|
554
825
|
const humanName = issue.reporter_display_name?.trim();
|
|
555
826
|
return humanName ? `${copy.originHumanLabel} \xB7 ${humanName}` : copy.originHumanLabel;
|
|
556
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
|
+
}
|
|
557
1205
|
function IssueReportModeBanner() {
|
|
558
1206
|
const { copy } = useIssueReporting();
|
|
559
1207
|
const reportMode = useReportMode();
|
|
560
1208
|
if (!reportMode?.isReportMode) {
|
|
561
1209
|
return null;
|
|
562
1210
|
}
|
|
563
|
-
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: [
|
|
564
1212
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "font-medium", children: copy.reportModeTitle }),
|
|
565
1213
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-amber-900/80", children: copy.reportModeDescription }),
|
|
566
1214
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
@@ -583,7 +1231,7 @@ function IssueList({
|
|
|
583
1231
|
const history = useIssueReportingHistory(filter);
|
|
584
1232
|
if (history.isPending) {
|
|
585
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: [
|
|
586
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1234
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Spinner, { className: "h-4 w-4 animate-spin" }),
|
|
587
1235
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copy.historyLoading })
|
|
588
1236
|
] });
|
|
589
1237
|
}
|
|
@@ -613,7 +1261,7 @@ function IssueList({
|
|
|
613
1261
|
{
|
|
614
1262
|
className: "rounded-2xl border border-slate-200 bg-white px-3 py-3 shadow-sm",
|
|
615
1263
|
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-start gap-3", children: [
|
|
616
|
-
/* @__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" }) }),
|
|
617
1265
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
618
1266
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [
|
|
619
1267
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "min-w-0", children: [
|
|
@@ -623,13 +1271,14 @@ function IssueList({
|
|
|
623
1271
|
"span",
|
|
624
1272
|
{
|
|
625
1273
|
className: cn(
|
|
626
|
-
"rounded-full px-2 py-0.5
|
|
1274
|
+
"rounded-full px-2 py-0.5 font-medium",
|
|
1275
|
+
BADGE_TEXT,
|
|
627
1276
|
getIssueStatusClassName(issue.status)
|
|
628
1277
|
),
|
|
629
1278
|
children: getIssueStatusBadgeLabel(issue.status)
|
|
630
1279
|
}
|
|
631
1280
|
),
|
|
632
|
-
/* @__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) }),
|
|
633
1282
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-xs text-slate-400", children: formatRelativeTime(issue.updated_at) })
|
|
634
1283
|
] })
|
|
635
1284
|
] }),
|
|
@@ -653,22 +1302,25 @@ function IssueList({
|
|
|
653
1302
|
}) });
|
|
654
1303
|
}
|
|
655
1304
|
function IssueReportPopover({ children }) {
|
|
656
|
-
const [filter, setFilter] = (0,
|
|
1305
|
+
const [filter, setFilter] = (0, import_react5.useState)("all");
|
|
657
1306
|
const {
|
|
658
1307
|
copy,
|
|
659
1308
|
isPopoverOpen,
|
|
660
1309
|
openPopover,
|
|
661
1310
|
closePopover,
|
|
662
1311
|
enterReportMode,
|
|
1312
|
+
openPageIssueModal,
|
|
663
1313
|
openExistingIssueModal,
|
|
664
1314
|
scope,
|
|
665
1315
|
setScope,
|
|
666
|
-
allowTenantScope
|
|
1316
|
+
allowTenantScope,
|
|
1317
|
+
createMode,
|
|
1318
|
+
hasRegisteredTargets
|
|
667
1319
|
} = useIssueReporting();
|
|
668
1320
|
const history = useIssueReportingHistory("all");
|
|
669
1321
|
const status = useIssueReportingStatus();
|
|
670
1322
|
const allItems = history.data?.items ?? [];
|
|
671
|
-
const counts = (0,
|
|
1323
|
+
const counts = (0, import_react5.useMemo)(
|
|
672
1324
|
() => ({
|
|
673
1325
|
all: allItems.length,
|
|
674
1326
|
unresolved: filterIssueReports(allItems, "unresolved").length,
|
|
@@ -677,6 +1329,9 @@ function IssueReportPopover({ children }) {
|
|
|
677
1329
|
[allItems]
|
|
678
1330
|
);
|
|
679
1331
|
const statusSummary = status.data ? `${status.data.open_count} open \xB7 ${status.data.recent_resolved_count} recently resolved` : "Status reflects the active scope.";
|
|
1332
|
+
const showSectionFirst = createMode === "surface_required" || createMode === "surface_preferred" && hasRegisteredTargets;
|
|
1333
|
+
const sectionActionDisabled = !hasRegisteredTargets;
|
|
1334
|
+
const helperText = createMode === "surface_required" ? hasRegisteredTargets ? copy.surfaceRequiredDescription : copy.specificSectionUnavailableDescription : createMode === "surface_preferred" && !hasRegisteredTargets ? copy.specificSectionUnavailableDescription : null;
|
|
680
1335
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
681
1336
|
Popover.Root,
|
|
682
1337
|
{
|
|
@@ -690,26 +1345,78 @@ function IssueReportPopover({ children }) {
|
|
|
690
1345
|
align: "end",
|
|
691
1346
|
side: "top",
|
|
692
1347
|
sideOffset: 12,
|
|
693
|
-
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
|
+
),
|
|
694
1354
|
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-4", children: [
|
|
695
1355
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between gap-3", children: [
|
|
696
1356
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
697
1357
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "text-sm font-semibold text-slate-900", children: copy.popoverTitle }),
|
|
698
1358
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-1 text-xs text-slate-500", children: statusSummary })
|
|
699
1359
|
] }),
|
|
700
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1360
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-wrap justify-end gap-2", children: showSectionFirst ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1361
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1362
|
+
"button",
|
|
1363
|
+
{
|
|
1364
|
+
type: "button",
|
|
1365
|
+
className: cn(
|
|
1366
|
+
"rounded-full px-3 py-2 text-xs font-semibold transition",
|
|
1367
|
+
sectionActionDisabled ? "cursor-not-allowed bg-slate-200 text-slate-500" : "bg-slate-900 text-white hover:bg-slate-800"
|
|
1368
|
+
),
|
|
1369
|
+
onClick: () => {
|
|
1370
|
+
if (sectionActionDisabled) {
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
closePopover();
|
|
1374
|
+
enterReportMode();
|
|
1375
|
+
},
|
|
1376
|
+
disabled: sectionActionDisabled,
|
|
1377
|
+
children: createMode === "surface_required" ? copy.reportNewAction : copy.reportSpecificAction
|
|
1378
|
+
}
|
|
1379
|
+
),
|
|
1380
|
+
createMode !== "surface_required" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1381
|
+
"button",
|
|
1382
|
+
{
|
|
1383
|
+
type: "button",
|
|
1384
|
+
className: "rounded-full border border-slate-200 px-3 py-2 text-xs font-semibold text-slate-700 transition hover:bg-slate-50",
|
|
1385
|
+
onClick: () => {
|
|
1386
|
+
closePopover();
|
|
1387
|
+
openPageIssueModal();
|
|
1388
|
+
},
|
|
1389
|
+
children: copy.reportPageAction
|
|
1390
|
+
}
|
|
1391
|
+
) : null
|
|
1392
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1393
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1394
|
+
"button",
|
|
1395
|
+
{
|
|
1396
|
+
type: "button",
|
|
1397
|
+
className: "rounded-full bg-slate-900 px-3 py-2 text-xs font-semibold text-white transition hover:bg-slate-800",
|
|
1398
|
+
onClick: () => {
|
|
1399
|
+
closePopover();
|
|
1400
|
+
openPageIssueModal();
|
|
1401
|
+
},
|
|
1402
|
+
children: createMode === "general_page" ? copy.reportPageAction : copy.reportNewAction
|
|
1403
|
+
}
|
|
1404
|
+
),
|
|
1405
|
+
hasRegisteredTargets ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1406
|
+
"button",
|
|
1407
|
+
{
|
|
1408
|
+
type: "button",
|
|
1409
|
+
className: "rounded-full border border-slate-200 px-3 py-2 text-xs font-semibold text-slate-700 transition hover:bg-slate-50",
|
|
1410
|
+
onClick: () => {
|
|
1411
|
+
closePopover();
|
|
1412
|
+
enterReportMode();
|
|
1413
|
+
},
|
|
1414
|
+
children: copy.reportSpecificAction
|
|
1415
|
+
}
|
|
1416
|
+
) : null
|
|
1417
|
+
] }) })
|
|
712
1418
|
] }),
|
|
1419
|
+
helperText ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "rounded-2xl border border-amber-200 bg-amber-50 px-4 py-3 text-xs text-amber-900", children: helperText }) : null,
|
|
713
1420
|
status.error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
|
|
714
1421
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: status.error.message || copy.statusLoadFailed }),
|
|
715
1422
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
@@ -723,7 +1430,7 @@ function IssueReportPopover({ children }) {
|
|
|
723
1430
|
)
|
|
724
1431
|
] }) : null,
|
|
725
1432
|
allowTenantScope ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-2", children: [
|
|
726
|
-
/* @__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 }),
|
|
727
1434
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex gap-2", children: [
|
|
728
1435
|
["tenant", copy.scopeTenant],
|
|
729
1436
|
["mine", copy.scopeMine]
|
|
@@ -779,18 +1486,46 @@ function IssueReportPopover({ children }) {
|
|
|
779
1486
|
}
|
|
780
1487
|
function IssueReportModal() {
|
|
781
1488
|
const {
|
|
1489
|
+
client,
|
|
782
1490
|
copy,
|
|
783
1491
|
reporterRoleHint,
|
|
784
1492
|
modalState,
|
|
785
1493
|
closeModal,
|
|
786
|
-
retryModalHydration
|
|
1494
|
+
retryModalHydration,
|
|
1495
|
+
inputModes,
|
|
1496
|
+
defaultInputMode,
|
|
1497
|
+
voice
|
|
787
1498
|
} = useIssueReporting();
|
|
788
1499
|
const { createMutation, updateMutation, replyMutation } = useIssueReportingMutations();
|
|
789
|
-
const [note, setNote] = (0,
|
|
790
|
-
const [submitError, setSubmitError] = (0,
|
|
1500
|
+
const [note, setNote] = (0, import_react5.useState)("");
|
|
1501
|
+
const [submitError, setSubmitError] = (0, import_react5.useState)(null);
|
|
791
1502
|
const { isOpen, mode, issue, target, isHydrating, error } = modalState;
|
|
792
|
-
|
|
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)(() => {
|
|
793
1527
|
if (!isOpen) {
|
|
1528
|
+
resetVoiceCapture();
|
|
794
1529
|
setNote("");
|
|
795
1530
|
setSubmitError(null);
|
|
796
1531
|
return;
|
|
@@ -800,140 +1535,145 @@ function IssueReportModal() {
|
|
|
800
1535
|
} else {
|
|
801
1536
|
setNote("");
|
|
802
1537
|
}
|
|
1538
|
+
resetVoiceCapture();
|
|
803
1539
|
setSubmitError(null);
|
|
804
|
-
}, [isOpen, mode,
|
|
805
|
-
const
|
|
806
|
-
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;
|
|
807
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
|
+
};
|
|
808
1558
|
const handleSubmit = async () => {
|
|
809
1559
|
if (!target || !isValid || isSubmitting) {
|
|
810
1560
|
return;
|
|
811
1561
|
}
|
|
812
1562
|
setSubmitError(null);
|
|
813
1563
|
try {
|
|
814
|
-
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;
|
|
815
1573
|
if (mode === "create") {
|
|
816
1574
|
await createMutation.mutateAsync({
|
|
817
|
-
target
|
|
818
|
-
|
|
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,
|
|
819
1589
|
reporter_role_hint: reporterRoleHint
|
|
820
1590
|
});
|
|
821
1591
|
} else if (mode === "edit" && issue) {
|
|
822
1592
|
await updateMutation.mutateAsync({
|
|
823
1593
|
issueReportId: issue.id,
|
|
824
|
-
note:
|
|
1594
|
+
note: noteForSubmit
|
|
825
1595
|
});
|
|
826
1596
|
} else if (mode === "reply" && issue) {
|
|
827
1597
|
await replyMutation.mutateAsync({
|
|
828
1598
|
issueReportId: issue.id,
|
|
829
|
-
note:
|
|
1599
|
+
note: noteForSubmit,
|
|
830
1600
|
reporterRoleHint
|
|
831
1601
|
});
|
|
832
1602
|
}
|
|
1603
|
+
resetVoiceCapture();
|
|
833
1604
|
closeModal();
|
|
834
1605
|
} catch (submissionError) {
|
|
835
1606
|
setSubmitError(resolveErrorMessage(submissionError, "Failed to submit issue report"));
|
|
836
1607
|
}
|
|
837
1608
|
};
|
|
838
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Root, { open: isOpen, onOpenChange: (open) => !open &&
|
|
839
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Overlay, { className: "fixed inset-0
|
|
840
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
"button",
|
|
855
|
-
{
|
|
856
|
-
type: "button",
|
|
857
|
-
className: "rounded-full border border-rose-300 px-3 py-1 font-medium transition hover:bg-rose-100",
|
|
858
|
-
onClick: () => retryModalHydration(),
|
|
859
|
-
children: copy.retryAction
|
|
860
|
-
}
|
|
861
|
-
),
|
|
862
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
863
|
-
"button",
|
|
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,
|
|
864
1625
|
{
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
children: copy.cancelAction
|
|
1626
|
+
copy,
|
|
1627
|
+
mode,
|
|
1628
|
+
target
|
|
869
1629
|
}
|
|
870
|
-
)
|
|
871
|
-
] })
|
|
872
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
873
|
-
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: [
|
|
874
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: copy.originalIssueLabel }),
|
|
875
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-1 text-sm text-slate-700", children: issue.note })
|
|
876
|
-
] }) : null,
|
|
877
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 space-y-2", children: [
|
|
1630
|
+
) }),
|
|
878
1631
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
879
|
-
|
|
1632
|
+
IssueReportModalBody,
|
|
880
1633
|
{
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
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()
|
|
910
1663
|
}
|
|
911
1664
|
),
|
|
912
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1665
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
913
1666
|
"button",
|
|
914
1667
|
{
|
|
915
1668
|
type: "button",
|
|
916
|
-
className:
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
),
|
|
920
|
-
onClick: () => void handleSubmit(),
|
|
921
|
-
disabled: !isValid || isSubmitting,
|
|
922
|
-
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" })
|
|
923
1672
|
}
|
|
924
|
-
)
|
|
925
|
-
]
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
"button",
|
|
929
|
-
{
|
|
930
|
-
type: "button",
|
|
931
|
-
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",
|
|
932
|
-
"aria-label": "Close issue report modal",
|
|
933
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react3.X, { className: "h-4 w-4" })
|
|
934
|
-
}
|
|
935
|
-
) })
|
|
936
|
-
] })
|
|
1673
|
+
) })
|
|
1674
|
+
]
|
|
1675
|
+
}
|
|
1676
|
+
)
|
|
937
1677
|
] }) });
|
|
938
1678
|
}
|
|
939
1679
|
function FloatingIssueReportButton({
|
|
@@ -955,7 +1695,7 @@ function FloatingIssueReportButton({
|
|
|
955
1695
|
}
|
|
956
1696
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
957
1697
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IssueReportModeBanner, {}),
|
|
958
|
-
!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)(
|
|
959
1699
|
"button",
|
|
960
1700
|
{
|
|
961
1701
|
type: "button",
|
|
@@ -967,7 +1707,7 @@ function FloatingIssueReportButton({
|
|
|
967
1707
|
className
|
|
968
1708
|
),
|
|
969
1709
|
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
970
|
-
|
|
1710
|
+
import_react4.BugBeetle,
|
|
971
1711
|
{
|
|
972
1712
|
className: cn("h-6 w-6", getEntryPointClassName(entryPointState)),
|
|
973
1713
|
weight: "fill"
|
|
@@ -986,9 +1726,23 @@ function ReportableSection({
|
|
|
986
1726
|
}) {
|
|
987
1727
|
const reportMode = useReportMode();
|
|
988
1728
|
const isSelectable = Boolean(reportMode?.isReportMode);
|
|
989
|
-
|
|
1729
|
+
const targetId = import_react5.default.useRef(/* @__PURE__ */ Symbol("reportable-section"));
|
|
1730
|
+
const elementRef = import_react5.default.useRef(null);
|
|
1731
|
+
(0, import_react5.useEffect)(() => {
|
|
1732
|
+
if (!reportMode) {
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
reportMode.registerTarget(targetId.current, reportableName, () => elementRef.current);
|
|
1736
|
+
return () => {
|
|
1737
|
+
reportMode.unregisterTarget(targetId.current);
|
|
1738
|
+
};
|
|
1739
|
+
}, [reportMode, reportableName]);
|
|
1740
|
+
return import_react5.default.createElement(
|
|
990
1741
|
Component,
|
|
991
1742
|
{
|
|
1743
|
+
ref: (node) => {
|
|
1744
|
+
elementRef.current = node;
|
|
1745
|
+
},
|
|
992
1746
|
className: cn(
|
|
993
1747
|
className,
|
|
994
1748
|
isSelectable && "cursor-pointer ring-2 ring-amber-400 transition hover:ring-amber-500"
|
|
@@ -1003,14 +1757,15 @@ function ReportableSection({
|
|
|
1003
1757
|
}
|
|
1004
1758
|
} : void 0,
|
|
1005
1759
|
role: isSelectable ? "button" : void 0,
|
|
1006
|
-
tabIndex: isSelectable ? 0 : void 0
|
|
1007
|
-
|
|
1008
|
-
|
|
1760
|
+
tabIndex: isSelectable ? 0 : void 0
|
|
1761
|
+
},
|
|
1762
|
+
children
|
|
1009
1763
|
);
|
|
1010
1764
|
}
|
|
1011
1765
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1012
1766
|
0 && (module.exports = {
|
|
1013
1767
|
FloatingIssueReportButton,
|
|
1768
|
+
IssueReportingPageConfig,
|
|
1014
1769
|
IssueReportingProvider,
|
|
1015
1770
|
ReportModeContext,
|
|
1016
1771
|
ReportableSection,
|