spaps-issue-reporting-react 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +90 -3
- package/dist/VoiceCapture-WX7J6BWQ.mjs +255 -0
- package/dist/index.d.mts +9 -138
- package/dist/index.d.ts +9 -138
- package/dist/index.js +970 -424
- package/dist/index.mjs +220 -224
- package/dist/screenshot-capture.d.mts +19 -0
- package/dist/screenshot-capture.d.ts +19 -0
- package/dist/screenshot-capture.js +315 -0
- package/dist/screenshot-capture.mjs +276 -0
- package/dist/types-D9U13O2X.d.mts +174 -0
- package/dist/types-D9U13O2X.d.ts +174 -0
- package/package.json +24 -5
package/dist/index.js
CHANGED
|
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
8
11
|
var __export = (target, all) => {
|
|
9
12
|
for (var name in all)
|
|
10
13
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -27,6 +30,553 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
30
|
));
|
|
28
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
32
|
|
|
33
|
+
// src/voice/VoiceCapture.tsx
|
|
34
|
+
var VoiceCapture_exports = {};
|
|
35
|
+
__export(VoiceCapture_exports, {
|
|
36
|
+
VoiceCapture: () => VoiceCapture,
|
|
37
|
+
default: () => VoiceCapture_default
|
|
38
|
+
});
|
|
39
|
+
function cn(...values) {
|
|
40
|
+
return values.filter(Boolean).join(" ");
|
|
41
|
+
}
|
|
42
|
+
function resolveErrorMessage(error, fallback) {
|
|
43
|
+
if (error instanceof Error && error.message.trim()) {
|
|
44
|
+
return error.message;
|
|
45
|
+
}
|
|
46
|
+
if (error && typeof error === "object" && "message" in error && typeof error.message === "string" && error.message.trim()) {
|
|
47
|
+
return error.message;
|
|
48
|
+
}
|
|
49
|
+
return fallback;
|
|
50
|
+
}
|
|
51
|
+
function getCommittedTranscriptText(committedTranscripts) {
|
|
52
|
+
return committedTranscripts.map((segment) => segment.text.trim()).filter(Boolean).join(" ").trim();
|
|
53
|
+
}
|
|
54
|
+
function IssueReportVoicePanel({
|
|
55
|
+
canUseText,
|
|
56
|
+
effectiveInputMode,
|
|
57
|
+
isSubmitting,
|
|
58
|
+
isVoiceActive,
|
|
59
|
+
isVoiceConnecting,
|
|
60
|
+
voice,
|
|
61
|
+
voiceTokenResult,
|
|
62
|
+
committedTranscript,
|
|
63
|
+
voiceError,
|
|
64
|
+
scribeError,
|
|
65
|
+
onSelectText,
|
|
66
|
+
onSelectVoice,
|
|
67
|
+
onStartVoiceInput,
|
|
68
|
+
onStopVoiceInput,
|
|
69
|
+
onAppendTranscript
|
|
70
|
+
}) {
|
|
71
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
72
|
+
canUseText ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 flex gap-2", children: [
|
|
73
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
74
|
+
"button",
|
|
75
|
+
{
|
|
76
|
+
type: "button",
|
|
77
|
+
className: cn(
|
|
78
|
+
"inline-flex items-center gap-1 rounded-full border px-3 py-1.5 text-xs font-semibold transition",
|
|
79
|
+
effectiveInputMode === "text" ? "border-slate-900 bg-slate-900 text-white" : "border-slate-200 text-slate-700 hover:bg-slate-50"
|
|
80
|
+
),
|
|
81
|
+
onClick: onSelectText,
|
|
82
|
+
disabled: isSubmitting,
|
|
83
|
+
children: [
|
|
84
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.TextT, { className: "h-3.5 w-3.5" }),
|
|
85
|
+
"Text Input"
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
),
|
|
89
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
90
|
+
"button",
|
|
91
|
+
{
|
|
92
|
+
type: "button",
|
|
93
|
+
className: cn(
|
|
94
|
+
"inline-flex items-center gap-1 rounded-full border px-3 py-1.5 text-xs font-semibold transition",
|
|
95
|
+
effectiveInputMode === "voice" ? "border-slate-900 bg-slate-900 text-white" : "border-slate-200 text-slate-700 hover:bg-slate-50"
|
|
96
|
+
),
|
|
97
|
+
onClick: onSelectVoice,
|
|
98
|
+
disabled: isSubmitting,
|
|
99
|
+
children: [
|
|
100
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Microphone, { className: "h-3.5 w-3.5" }),
|
|
101
|
+
"Voice Input"
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
] }) : null,
|
|
106
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-4 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: [
|
|
107
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
108
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
109
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: "Voice Input" }),
|
|
110
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "mt-1 text-xs text-slate-600", children: [
|
|
111
|
+
"Provider: ",
|
|
112
|
+
voiceTokenResult?.provider ?? voice.provider,
|
|
113
|
+
" \xB7 Model:",
|
|
114
|
+
" ",
|
|
115
|
+
voiceTokenResult?.model_id ?? voice.modelId
|
|
116
|
+
] }),
|
|
117
|
+
/* @__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." })
|
|
118
|
+
] }),
|
|
119
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2", children: [
|
|
120
|
+
isVoiceActive || isVoiceConnecting ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
121
|
+
"button",
|
|
122
|
+
{
|
|
123
|
+
type: "button",
|
|
124
|
+
className: "rounded-full border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-100",
|
|
125
|
+
onClick: onStopVoiceInput,
|
|
126
|
+
disabled: isSubmitting,
|
|
127
|
+
children: "Stop Voice Input"
|
|
128
|
+
}
|
|
129
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
130
|
+
"button",
|
|
131
|
+
{
|
|
132
|
+
type: "button",
|
|
133
|
+
className: "rounded-full bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-slate-800",
|
|
134
|
+
onClick: onStartVoiceInput,
|
|
135
|
+
disabled: isSubmitting,
|
|
136
|
+
children: "Start Voice Input"
|
|
137
|
+
}
|
|
138
|
+
),
|
|
139
|
+
canUseText ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
140
|
+
"button",
|
|
141
|
+
{
|
|
142
|
+
type: "button",
|
|
143
|
+
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",
|
|
144
|
+
onClick: onAppendTranscript,
|
|
145
|
+
disabled: isSubmitting || !committedTranscript.trim(),
|
|
146
|
+
children: "Append Transcript"
|
|
147
|
+
}
|
|
148
|
+
) : null
|
|
149
|
+
] })
|
|
150
|
+
] }),
|
|
151
|
+
/* @__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." }) }),
|
|
152
|
+
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.jsxs)("div", { className: "mt-2 flex items-center gap-1.5 text-xs text-slate-500", children: [
|
|
153
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Spinner, { className: "h-3.5 w-3.5 animate-spin" }),
|
|
154
|
+
"Connecting voice transcription..."
|
|
155
|
+
] }) : null
|
|
156
|
+
] })
|
|
157
|
+
] });
|
|
158
|
+
}
|
|
159
|
+
function VoiceCapture({
|
|
160
|
+
canUseText,
|
|
161
|
+
effectiveInputMode,
|
|
162
|
+
isSubmitting,
|
|
163
|
+
voice,
|
|
164
|
+
createVoiceToken,
|
|
165
|
+
startRequestId,
|
|
166
|
+
appendRequestId,
|
|
167
|
+
onSelectText,
|
|
168
|
+
onSelectVoice,
|
|
169
|
+
onTranscriptCommitted,
|
|
170
|
+
onSubmitMetadataChange,
|
|
171
|
+
onAppendTranscript
|
|
172
|
+
}) {
|
|
173
|
+
const [voiceTokenResult, setVoiceTokenResult] = (0, import_react5.useState)(null);
|
|
174
|
+
const [voiceSubmitMetadata, setVoiceSubmitMetadata] = (0, import_react5.useState)(null);
|
|
175
|
+
const [voiceError, setVoiceError] = (0, import_react5.useState)(null);
|
|
176
|
+
const startInFlightRef = (0, import_react5.useRef)(false);
|
|
177
|
+
const lastStartRequestId = (0, import_react5.useRef)(0);
|
|
178
|
+
const lastAppendRequestId = (0, import_react5.useRef)(0);
|
|
179
|
+
const {
|
|
180
|
+
status: scribeStatus,
|
|
181
|
+
isConnected,
|
|
182
|
+
isTranscribing,
|
|
183
|
+
committedTranscripts,
|
|
184
|
+
error: scribeError,
|
|
185
|
+
connect: connectScribe,
|
|
186
|
+
disconnect: disconnectScribe
|
|
187
|
+
} = (0, import_react3.useScribe)({
|
|
188
|
+
autoConnect: false,
|
|
189
|
+
modelId: voice.modelId,
|
|
190
|
+
microphone: voice.microphone
|
|
191
|
+
});
|
|
192
|
+
const committedTranscript = (0, import_react5.useMemo)(
|
|
193
|
+
() => getCommittedTranscriptText(committedTranscripts),
|
|
194
|
+
[committedTranscripts]
|
|
195
|
+
);
|
|
196
|
+
const isVoiceConnecting = scribeStatus === "connecting";
|
|
197
|
+
const isVoiceActive = isConnected || isTranscribing;
|
|
198
|
+
(0, import_react5.useEffect)(() => {
|
|
199
|
+
onTranscriptCommitted(committedTranscript);
|
|
200
|
+
}, [committedTranscript, onTranscriptCommitted]);
|
|
201
|
+
(0, import_react5.useEffect)(() => {
|
|
202
|
+
onSubmitMetadataChange(voiceSubmitMetadata);
|
|
203
|
+
}, [onSubmitMetadataChange, voiceSubmitMetadata]);
|
|
204
|
+
(0, import_react5.useEffect)(() => {
|
|
205
|
+
return () => {
|
|
206
|
+
disconnectScribe();
|
|
207
|
+
};
|
|
208
|
+
}, [disconnectScribe]);
|
|
209
|
+
const startVoiceInput = (0, import_react5.useCallback)(async () => {
|
|
210
|
+
if (startInFlightRef.current || isSubmitting || isVoiceActive || isVoiceConnecting) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (!createVoiceToken) {
|
|
214
|
+
setVoiceError("Voice input is unavailable for this client.");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
startInFlightRef.current = true;
|
|
218
|
+
setVoiceError(null);
|
|
219
|
+
try {
|
|
220
|
+
const tokenResult = await createVoiceToken();
|
|
221
|
+
setVoiceTokenResult(tokenResult);
|
|
222
|
+
setVoiceSubmitMetadata(tokenResult);
|
|
223
|
+
await connectScribe({
|
|
224
|
+
token: tokenResult.token,
|
|
225
|
+
modelId: tokenResult.model_id,
|
|
226
|
+
microphone: voice.microphone
|
|
227
|
+
});
|
|
228
|
+
onSelectVoice();
|
|
229
|
+
} catch (startError) {
|
|
230
|
+
setVoiceError(resolveErrorMessage(startError, "Failed to start voice input."));
|
|
231
|
+
} finally {
|
|
232
|
+
startInFlightRef.current = false;
|
|
233
|
+
}
|
|
234
|
+
}, [
|
|
235
|
+
connectScribe,
|
|
236
|
+
createVoiceToken,
|
|
237
|
+
isSubmitting,
|
|
238
|
+
isVoiceActive,
|
|
239
|
+
isVoiceConnecting,
|
|
240
|
+
onSelectVoice,
|
|
241
|
+
voice.microphone
|
|
242
|
+
]);
|
|
243
|
+
const appendTranscript = (0, import_react5.useCallback)(() => {
|
|
244
|
+
if (!committedTranscript.trim()) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
onAppendTranscript(committedTranscript);
|
|
248
|
+
}, [committedTranscript, onAppendTranscript]);
|
|
249
|
+
(0, import_react5.useEffect)(() => {
|
|
250
|
+
if (startRequestId === 0 || startRequestId === lastStartRequestId.current) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
lastStartRequestId.current = startRequestId;
|
|
254
|
+
void startVoiceInput();
|
|
255
|
+
}, [startRequestId, startVoiceInput]);
|
|
256
|
+
(0, import_react5.useEffect)(() => {
|
|
257
|
+
if (appendRequestId === 0 || appendRequestId === lastAppendRequestId.current) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
lastAppendRequestId.current = appendRequestId;
|
|
261
|
+
appendTranscript();
|
|
262
|
+
}, [appendRequestId, appendTranscript]);
|
|
263
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
264
|
+
IssueReportVoicePanel,
|
|
265
|
+
{
|
|
266
|
+
canUseText,
|
|
267
|
+
effectiveInputMode,
|
|
268
|
+
isSubmitting,
|
|
269
|
+
isVoiceActive,
|
|
270
|
+
isVoiceConnecting,
|
|
271
|
+
voice,
|
|
272
|
+
voiceTokenResult,
|
|
273
|
+
committedTranscript,
|
|
274
|
+
voiceError,
|
|
275
|
+
scribeError,
|
|
276
|
+
onSelectText,
|
|
277
|
+
onSelectVoice,
|
|
278
|
+
onStartVoiceInput: () => void startVoiceInput(),
|
|
279
|
+
onStopVoiceInput: disconnectScribe,
|
|
280
|
+
onAppendTranscript: appendTranscript
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
var import_react3, import_react4, import_react5, import_jsx_runtime2, VoiceCapture_default;
|
|
285
|
+
var init_VoiceCapture = __esm({
|
|
286
|
+
"src/voice/VoiceCapture.tsx"() {
|
|
287
|
+
"use strict";
|
|
288
|
+
import_react3 = require("@elevenlabs/react");
|
|
289
|
+
import_react4 = require("@phosphor-icons/react");
|
|
290
|
+
import_react5 = require("react");
|
|
291
|
+
import_jsx_runtime2 = require("react/jsx-runtime");
|
|
292
|
+
VoiceCapture_default = VoiceCapture;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// src/screenshot-capture.ts
|
|
297
|
+
var screenshot_capture_exports = {};
|
|
298
|
+
__export(screenshot_capture_exports, {
|
|
299
|
+
ATTACHMENT_MAX_BYTES: () => ATTACHMENT_MAX_BYTES2,
|
|
300
|
+
SCREENSHOT_CAPTURE_REDACTION_TEXT: () => SCREENSHOT_CAPTURE_REDACTION_TEXT,
|
|
301
|
+
captureScreenshot: () => captureScreenshot,
|
|
302
|
+
maskScreenshotCaptureDocument: () => maskScreenshotCaptureDocument,
|
|
303
|
+
resetHtml2CanvasCache: () => resetHtml2CanvasCache
|
|
304
|
+
});
|
|
305
|
+
function loadHtml2Canvas() {
|
|
306
|
+
if (!html2canvasPromise) {
|
|
307
|
+
html2canvasPromise = import("html2canvas");
|
|
308
|
+
}
|
|
309
|
+
return html2canvasPromise;
|
|
310
|
+
}
|
|
311
|
+
function resolveFilename(config, context) {
|
|
312
|
+
const imageType = config.imageType ?? "image/png";
|
|
313
|
+
const ext = MIME_TO_EXTENSION[imageType] ?? "png";
|
|
314
|
+
if (typeof config.filename === "function") {
|
|
315
|
+
return config.filename(context);
|
|
316
|
+
}
|
|
317
|
+
if (typeof config.filename === "string") {
|
|
318
|
+
return config.filename;
|
|
319
|
+
}
|
|
320
|
+
return `screenshot-${context.target.componentKey}-${Date.now()}.${ext}`;
|
|
321
|
+
}
|
|
322
|
+
function resolveRootElement(config) {
|
|
323
|
+
if (typeof config.rootElement === "function") {
|
|
324
|
+
return config.rootElement();
|
|
325
|
+
}
|
|
326
|
+
if (typeof HTMLElement !== "undefined" && config.rootElement instanceof HTMLElement) {
|
|
327
|
+
return config.rootElement;
|
|
328
|
+
}
|
|
329
|
+
return document.documentElement;
|
|
330
|
+
}
|
|
331
|
+
function buildIgnoreSelectors(config) {
|
|
332
|
+
const selectors = [...DEFAULT_EXCLUDE_SELECTORS];
|
|
333
|
+
if (config.excludeSelectors) {
|
|
334
|
+
selectors.push(...config.excludeSelectors.map((selector) => selector.trim()));
|
|
335
|
+
}
|
|
336
|
+
return selectors.filter(Boolean);
|
|
337
|
+
}
|
|
338
|
+
function safelyMatchesSelector(element, selector) {
|
|
339
|
+
try {
|
|
340
|
+
return element.matches(selector);
|
|
341
|
+
} catch {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function buildIgnoreElementPredicate(config) {
|
|
346
|
+
const selectors = buildIgnoreSelectors(config);
|
|
347
|
+
return (element) => selectors.some((selector) => safelyMatchesSelector(element, selector));
|
|
348
|
+
}
|
|
349
|
+
function canvasToBlobAsync(canvas, mimeType, quality) {
|
|
350
|
+
return new Promise((resolve) => {
|
|
351
|
+
canvas.toBlob(resolve, mimeType, quality);
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
async function canvasToSizedBlob(canvas, imageType, quality, maxBytes) {
|
|
355
|
+
const initial = await canvasToBlobAsync(canvas, imageType, quality);
|
|
356
|
+
if (initial && initial.size <= maxBytes) {
|
|
357
|
+
return { blob: initial, scaled: false };
|
|
358
|
+
}
|
|
359
|
+
const fallbackTypes = imageType === "image/png" ? ["image/webp", "image/jpeg"] : [imageType, "image/webp", "image/jpeg"];
|
|
360
|
+
let currentCanvas = canvas;
|
|
361
|
+
let scaled = false;
|
|
362
|
+
for (const scale of [0.85, 0.7, 0.55, 0.4]) {
|
|
363
|
+
currentCanvas = scaleCanvas(currentCanvas, scale);
|
|
364
|
+
scaled = true;
|
|
365
|
+
for (const format of fallbackTypes) {
|
|
366
|
+
for (const candidateQuality of [0.86, 0.72, 0.58, 0.44, 0.32]) {
|
|
367
|
+
const attempt = await canvasToBlobAsync(
|
|
368
|
+
currentCanvas,
|
|
369
|
+
format,
|
|
370
|
+
candidateQuality
|
|
371
|
+
);
|
|
372
|
+
if (attempt && attempt.size <= maxBytes) {
|
|
373
|
+
return { blob: attempt, scaled };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
throw new Error(
|
|
379
|
+
`Screenshot exceeds ${Math.round(maxBytes / 1024 / 1024)} MiB after compression`
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
async function captureScreenshot(config, context) {
|
|
383
|
+
if (!config.enabled) {
|
|
384
|
+
throw new Error("Screenshot capture is not enabled");
|
|
385
|
+
}
|
|
386
|
+
if (!canUseBrowserCapture()) {
|
|
387
|
+
throw new Error("Browser screenshot capture is unavailable");
|
|
388
|
+
}
|
|
389
|
+
const { default: html2canvas } = await loadHtml2Canvas();
|
|
390
|
+
const rootElement = resolveRootElement(config);
|
|
391
|
+
if (!rootElement) {
|
|
392
|
+
throw new Error("Screenshot root element not found");
|
|
393
|
+
}
|
|
394
|
+
const ignoreElements = buildIgnoreElementPredicate(config);
|
|
395
|
+
const imageType = config.imageType ?? "image/png";
|
|
396
|
+
const maxBytes = Math.min(config.maxBytes ?? ATTACHMENT_MAX_BYTES2, ATTACHMENT_MAX_BYTES2);
|
|
397
|
+
const viewport = readViewport();
|
|
398
|
+
const rect = resolveCaptureRect(config, viewport);
|
|
399
|
+
const pixelRatio = Math.min(
|
|
400
|
+
Math.max(config.pixelRatio ?? window.devicePixelRatio ?? 1, 1),
|
|
401
|
+
2
|
|
402
|
+
);
|
|
403
|
+
const ignoreSelectors = buildIgnoreSelectors(config);
|
|
404
|
+
const canvas = await html2canvas(rootElement, {
|
|
405
|
+
useCORS: true,
|
|
406
|
+
allowTaint: false,
|
|
407
|
+
backgroundColor: null,
|
|
408
|
+
logging: false,
|
|
409
|
+
ignoreElements,
|
|
410
|
+
onclone: (clonedDocument) => {
|
|
411
|
+
maskScreenshotCaptureDocument(
|
|
412
|
+
clonedDocument,
|
|
413
|
+
ignoreSelectors,
|
|
414
|
+
config.maskSelectors
|
|
415
|
+
);
|
|
416
|
+
},
|
|
417
|
+
removeContainer: true,
|
|
418
|
+
scale: pixelRatio,
|
|
419
|
+
scrollX: window.scrollX,
|
|
420
|
+
scrollY: window.scrollY,
|
|
421
|
+
width: rect.width,
|
|
422
|
+
height: rect.height,
|
|
423
|
+
windowWidth: viewport.width,
|
|
424
|
+
windowHeight: viewport.height,
|
|
425
|
+
x: rect.left + window.scrollX,
|
|
426
|
+
y: rect.top + window.scrollY
|
|
427
|
+
});
|
|
428
|
+
const output = await canvasToSizedBlob(
|
|
429
|
+
canvas,
|
|
430
|
+
imageType,
|
|
431
|
+
config.quality,
|
|
432
|
+
maxBytes
|
|
433
|
+
);
|
|
434
|
+
const filename = resolveFilename(config, context);
|
|
435
|
+
return {
|
|
436
|
+
blob: new Blob([output.blob], { type: output.blob.type || imageType }),
|
|
437
|
+
filename: replaceFilenameExtension(
|
|
438
|
+
filename,
|
|
439
|
+
MIME_TO_EXTENSION[output.blob.type || imageType] ?? MIME_TO_EXTENSION[imageType]
|
|
440
|
+
),
|
|
441
|
+
mimeType: output.blob.type || imageType,
|
|
442
|
+
byteSize: output.blob.size,
|
|
443
|
+
rect,
|
|
444
|
+
scaled: output.scaled
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
function resetHtml2CanvasCache() {
|
|
448
|
+
html2canvasPromise = null;
|
|
449
|
+
}
|
|
450
|
+
function maskScreenshotCaptureDocument(clonedDocument, excludeSelectors = DEFAULT_EXCLUDE_SELECTORS, maskSelectors = DEFAULT_MASK_SELECTORS) {
|
|
451
|
+
clonedDocument.querySelectorAll([...excludeSelectors, "script"].join(",")).forEach((element) => element.remove());
|
|
452
|
+
clonedDocument.querySelectorAll(maskSelectors.join(",")).forEach(maskCaptureElement);
|
|
453
|
+
redactSensitiveTextNodes(clonedDocument.body ?? clonedDocument.documentElement);
|
|
454
|
+
}
|
|
455
|
+
function canUseBrowserCapture() {
|
|
456
|
+
return typeof window !== "undefined" && typeof document !== "undefined" && typeof Blob !== "undefined" && typeof document.createElement === "function";
|
|
457
|
+
}
|
|
458
|
+
function readViewport() {
|
|
459
|
+
return {
|
|
460
|
+
width: Math.max(
|
|
461
|
+
1,
|
|
462
|
+
Math.round(window.innerWidth || document.documentElement.clientWidth || 1)
|
|
463
|
+
),
|
|
464
|
+
height: Math.max(
|
|
465
|
+
1,
|
|
466
|
+
Math.round(window.innerHeight || document.documentElement.clientHeight || 1)
|
|
467
|
+
)
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
function resolveCaptureRect(config, viewport) {
|
|
471
|
+
const configuredRect = typeof config.rect === "function" ? config.rect() : config.rect;
|
|
472
|
+
const source = configuredRect && configuredRect.width > 0 && configuredRect.height > 0 ? configuredRect : { top: 0, left: 0, width: viewport.width, height: viewport.height };
|
|
473
|
+
const left = clamp(Math.round(source.left), 0, viewport.width - 1);
|
|
474
|
+
const top = clamp(Math.round(source.top), 0, viewport.height - 1);
|
|
475
|
+
const right = clamp(
|
|
476
|
+
Math.round(source.left + source.width),
|
|
477
|
+
left + 1,
|
|
478
|
+
viewport.width
|
|
479
|
+
);
|
|
480
|
+
const bottom = clamp(
|
|
481
|
+
Math.round(source.top + source.height),
|
|
482
|
+
top + 1,
|
|
483
|
+
viewport.height
|
|
484
|
+
);
|
|
485
|
+
return {
|
|
486
|
+
left,
|
|
487
|
+
top,
|
|
488
|
+
width: right - left,
|
|
489
|
+
height: bottom - top
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
function clamp(value, min, max) {
|
|
493
|
+
return Math.min(Math.max(value, min), max);
|
|
494
|
+
}
|
|
495
|
+
function maskCaptureElement(element) {
|
|
496
|
+
if (element instanceof HTMLInputElement) {
|
|
497
|
+
element.value = "";
|
|
498
|
+
element.setAttribute("value", "");
|
|
499
|
+
element.setAttribute("placeholder", SCREENSHOT_CAPTURE_REDACTION_TEXT);
|
|
500
|
+
} else if (element instanceof HTMLTextAreaElement) {
|
|
501
|
+
element.value = "";
|
|
502
|
+
element.textContent = SCREENSHOT_CAPTURE_REDACTION_TEXT;
|
|
503
|
+
element.setAttribute("placeholder", SCREENSHOT_CAPTURE_REDACTION_TEXT);
|
|
504
|
+
} else if (element instanceof HTMLSelectElement) {
|
|
505
|
+
element.querySelectorAll("option").forEach((option) => {
|
|
506
|
+
option.textContent = SCREENSHOT_CAPTURE_REDACTION_TEXT;
|
|
507
|
+
});
|
|
508
|
+
} else {
|
|
509
|
+
element.textContent = SCREENSHOT_CAPTURE_REDACTION_TEXT;
|
|
510
|
+
}
|
|
511
|
+
if (element instanceof HTMLElement) {
|
|
512
|
+
element.setAttribute("aria-label", SCREENSHOT_CAPTURE_REDACTION_TEXT);
|
|
513
|
+
element.style.color = "#1d1914";
|
|
514
|
+
element.style.backgroundColor = "#d8d0c1";
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function redactSensitiveTextNodes(root) {
|
|
518
|
+
if (!root) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const ownerDocument = root.ownerDocument ?? document;
|
|
522
|
+
const walker = ownerDocument.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
523
|
+
let node = walker.nextNode();
|
|
524
|
+
while (node) {
|
|
525
|
+
if (node.textContent) {
|
|
526
|
+
node.textContent = redactSensitiveText(node.textContent);
|
|
527
|
+
}
|
|
528
|
+
node = walker.nextNode();
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function redactSensitiveText(value) {
|
|
532
|
+
return value.replace(
|
|
533
|
+
/\b((?:access|refresh|id)_token)=([^&#\s]+)/gi,
|
|
534
|
+
(_, key, token) => `${key}=${redactValue(token)}`
|
|
535
|
+
).replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]").replace(/\b[ps]k_(?:live|test)_[A-Za-z0-9_=-]+/g, SCREENSHOT_CAPTURE_REDACTION_TEXT).replace(/\b(?:token|secret|password|signature)[_-]?[A-Za-z0-9._~+/=-]{8,}/gi, SCREENSHOT_CAPTURE_REDACTION_TEXT);
|
|
536
|
+
}
|
|
537
|
+
function redactValue(value) {
|
|
538
|
+
return value ? SCREENSHOT_CAPTURE_REDACTION_TEXT : value;
|
|
539
|
+
}
|
|
540
|
+
function scaleCanvas(source, ratio) {
|
|
541
|
+
const canvas = document.createElement("canvas");
|
|
542
|
+
canvas.width = Math.max(1, Math.round(source.width * ratio));
|
|
543
|
+
canvas.height = Math.max(1, Math.round(source.height * ratio));
|
|
544
|
+
const context = canvas.getContext("2d");
|
|
545
|
+
if (!context) {
|
|
546
|
+
throw new Error("Canvas 2D context is unavailable");
|
|
547
|
+
}
|
|
548
|
+
context.drawImage(source, 0, 0, canvas.width, canvas.height);
|
|
549
|
+
return canvas;
|
|
550
|
+
}
|
|
551
|
+
function replaceFilenameExtension(filename, extension) {
|
|
552
|
+
return filename.replace(/\.(png|jpe?g|webp)$/i, `.${extension}`);
|
|
553
|
+
}
|
|
554
|
+
var DEFAULT_EXCLUDE_SELECTORS, DEFAULT_MASK_SELECTORS, SCREENSHOT_CAPTURE_REDACTION_TEXT, ATTACHMENT_MAX_BYTES2, MIME_TO_EXTENSION, html2canvasPromise;
|
|
555
|
+
var init_screenshot_capture = __esm({
|
|
556
|
+
"src/screenshot-capture.ts"() {
|
|
557
|
+
"use strict";
|
|
558
|
+
DEFAULT_EXCLUDE_SELECTORS = [
|
|
559
|
+
"[data-spaps-screenshot-exclude]",
|
|
560
|
+
"[data-spaps-private]"
|
|
561
|
+
];
|
|
562
|
+
DEFAULT_MASK_SELECTORS = [
|
|
563
|
+
"input",
|
|
564
|
+
"option",
|
|
565
|
+
"select",
|
|
566
|
+
"textarea",
|
|
567
|
+
'[contenteditable="true"]'
|
|
568
|
+
];
|
|
569
|
+
SCREENSHOT_CAPTURE_REDACTION_TEXT = "[redacted]";
|
|
570
|
+
ATTACHMENT_MAX_BYTES2 = 10 * 1024 * 1024;
|
|
571
|
+
MIME_TO_EXTENSION = {
|
|
572
|
+
"image/png": "png",
|
|
573
|
+
"image/jpeg": "jpg",
|
|
574
|
+
"image/webp": "webp"
|
|
575
|
+
};
|
|
576
|
+
html2canvasPromise = null;
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
|
|
30
580
|
// src/index.ts
|
|
31
581
|
var index_exports = {};
|
|
32
582
|
__export(index_exports, {
|
|
@@ -61,10 +611,9 @@ module.exports = __toCommonJS(index_exports);
|
|
|
61
611
|
// src/components.tsx
|
|
62
612
|
var Dialog = __toESM(require("@radix-ui/react-dialog"));
|
|
63
613
|
var Popover = __toESM(require("@radix-ui/react-popover"));
|
|
64
|
-
var
|
|
65
|
-
var import_react4 = require("@phosphor-icons/react");
|
|
614
|
+
var import_react6 = require("@phosphor-icons/react");
|
|
66
615
|
var import_date_fns = require("date-fns");
|
|
67
|
-
var
|
|
616
|
+
var import_react7 = __toESM(require("react"));
|
|
68
617
|
|
|
69
618
|
// src/provider.tsx
|
|
70
619
|
var import_react2 = require("react");
|
|
@@ -546,6 +1095,7 @@ function IssueReportingProvider({
|
|
|
546
1095
|
inputModes,
|
|
547
1096
|
defaultInputMode,
|
|
548
1097
|
voice,
|
|
1098
|
+
screenshotCapture,
|
|
549
1099
|
copy,
|
|
550
1100
|
children
|
|
551
1101
|
}) {
|
|
@@ -791,6 +1341,7 @@ function IssueReportingProvider({
|
|
|
791
1341
|
inputModes: resolvedInputModes,
|
|
792
1342
|
defaultInputMode: resolvedDefaultInputMode,
|
|
793
1343
|
voice: resolvedVoiceConfig,
|
|
1344
|
+
screenshotCapture,
|
|
794
1345
|
needsResponseIssueIds,
|
|
795
1346
|
setNeedsResponse
|
|
796
1347
|
}),
|
|
@@ -819,6 +1370,7 @@ function IssueReportingProvider({
|
|
|
819
1370
|
resolvedInputModes,
|
|
820
1371
|
resolvedVoiceConfig,
|
|
821
1372
|
scope,
|
|
1373
|
+
screenshotCapture,
|
|
822
1374
|
selectPanel,
|
|
823
1375
|
setNeedsResponse,
|
|
824
1376
|
startNewIssue
|
|
@@ -859,8 +1411,8 @@ function IssueReportingProvider({
|
|
|
859
1411
|
}
|
|
860
1412
|
|
|
861
1413
|
// src/components.tsx
|
|
862
|
-
var
|
|
863
|
-
function
|
|
1414
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1415
|
+
function cn2(...values) {
|
|
864
1416
|
return values.filter(Boolean).join(" ");
|
|
865
1417
|
}
|
|
866
1418
|
var Z_FLOATING_BUTTON = "z-[65]";
|
|
@@ -875,6 +1427,9 @@ var POPOVER_SHADOW = "shadow-[0_18px_48px_rgba(15,23,42,0.18)]";
|
|
|
875
1427
|
var MODAL_SHADOW = "shadow-[0_28px_80px_rgba(15,23,42,0.24)]";
|
|
876
1428
|
var BADGE_TEXT = "text-[11px]";
|
|
877
1429
|
var LABEL_TEXT = "text-[11px]";
|
|
1430
|
+
var VoiceCapture2 = import_react7.default.lazy(() => Promise.resolve().then(() => (init_VoiceCapture(), VoiceCapture_exports)));
|
|
1431
|
+
var useIsomorphicLayoutEffect = typeof window === "undefined" ? import_react7.useEffect : import_react7.default.useLayoutEffect;
|
|
1432
|
+
var SCREENSHOT_CAPTURE_METADATA_KEY = "screenshot_capture";
|
|
878
1433
|
function truncate(value, max = 80) {
|
|
879
1434
|
if (value.length <= max) {
|
|
880
1435
|
return value;
|
|
@@ -886,7 +1441,7 @@ function formatRelativeTime(timestamp) {
|
|
|
886
1441
|
addSuffix: true
|
|
887
1442
|
});
|
|
888
1443
|
}
|
|
889
|
-
function
|
|
1444
|
+
function resolveErrorMessage2(error, fallback) {
|
|
890
1445
|
if (error instanceof Error && error.message.trim()) {
|
|
891
1446
|
return error.message;
|
|
892
1447
|
}
|
|
@@ -895,8 +1450,13 @@ function resolveErrorMessage(error, fallback) {
|
|
|
895
1450
|
}
|
|
896
1451
|
return fallback;
|
|
897
1452
|
}
|
|
898
|
-
function
|
|
899
|
-
return
|
|
1453
|
+
function buildScreenshotCaptureMetadata(config, status) {
|
|
1454
|
+
return {
|
|
1455
|
+
status,
|
|
1456
|
+
scope: config.scope ?? "visible_viewport",
|
|
1457
|
+
image_type: config.imageType ?? "image/png",
|
|
1458
|
+
...status === "failed" ? { failure_reason: "capture_failed" } : {}
|
|
1459
|
+
};
|
|
900
1460
|
}
|
|
901
1461
|
function appendTranscriptToNote(current, transcript) {
|
|
902
1462
|
const normalizedCurrent = current.trim();
|
|
@@ -937,105 +1497,129 @@ function getIssueOriginText(issue, copy) {
|
|
|
937
1497
|
const humanName = issue.reporter_display_name?.trim();
|
|
938
1498
|
return humanName ? `${copy.originHumanLabel} \xB7 ${humanName}` : copy.originHumanLabel;
|
|
939
1499
|
}
|
|
940
|
-
function
|
|
1500
|
+
function IssueReportVoiceActivationPanel({
|
|
941
1501
|
canUseText,
|
|
942
1502
|
effectiveInputMode,
|
|
943
1503
|
isSubmitting,
|
|
944
|
-
isVoiceActive,
|
|
945
|
-
isVoiceConnecting,
|
|
946
|
-
voice,
|
|
947
|
-
voiceTokenResult,
|
|
948
|
-
committedTranscript,
|
|
949
|
-
voiceError,
|
|
950
|
-
scribeError,
|
|
951
1504
|
onSelectText,
|
|
952
1505
|
onSelectVoice,
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
onAppendTranscript
|
|
1506
|
+
onRequestStartVoiceInput,
|
|
1507
|
+
onRequestAppendTranscript
|
|
956
1508
|
}) {
|
|
957
|
-
return /* @__PURE__ */ (0,
|
|
958
|
-
canUseText ? /* @__PURE__ */ (0,
|
|
959
|
-
/* @__PURE__ */ (0,
|
|
1509
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
1510
|
+
canUseText ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-5 flex gap-2", children: [
|
|
1511
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
960
1512
|
"button",
|
|
961
1513
|
{
|
|
962
1514
|
type: "button",
|
|
963
|
-
className:
|
|
1515
|
+
className: cn2(
|
|
964
1516
|
"inline-flex items-center gap-1 rounded-full border px-3 py-1.5 text-xs font-semibold transition",
|
|
965
1517
|
effectiveInputMode === "text" ? "border-slate-900 bg-slate-900 text-white" : "border-slate-200 text-slate-700 hover:bg-slate-50"
|
|
966
1518
|
),
|
|
967
1519
|
onClick: onSelectText,
|
|
968
1520
|
disabled: isSubmitting,
|
|
969
1521
|
children: [
|
|
970
|
-
/* @__PURE__ */ (0,
|
|
1522
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.TextT, { className: "h-3.5 w-3.5" }),
|
|
971
1523
|
"Text Input"
|
|
972
1524
|
]
|
|
973
1525
|
}
|
|
974
1526
|
),
|
|
975
|
-
/* @__PURE__ */ (0,
|
|
1527
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
976
1528
|
"button",
|
|
977
1529
|
{
|
|
978
1530
|
type: "button",
|
|
979
|
-
className:
|
|
1531
|
+
className: cn2(
|
|
980
1532
|
"inline-flex items-center gap-1 rounded-full border px-3 py-1.5 text-xs font-semibold transition",
|
|
981
1533
|
effectiveInputMode === "voice" ? "border-slate-900 bg-slate-900 text-white" : "border-slate-200 text-slate-700 hover:bg-slate-50"
|
|
982
1534
|
),
|
|
983
1535
|
onClick: onSelectVoice,
|
|
984
1536
|
disabled: isSubmitting,
|
|
985
1537
|
children: [
|
|
986
|
-
/* @__PURE__ */ (0,
|
|
1538
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Microphone, { className: "h-3.5 w-3.5" }),
|
|
987
1539
|
"Voice Input"
|
|
988
1540
|
]
|
|
989
1541
|
}
|
|
990
1542
|
)
|
|
991
1543
|
] }) : null,
|
|
992
|
-
/* @__PURE__ */ (0,
|
|
993
|
-
/* @__PURE__ */ (0,
|
|
994
|
-
/* @__PURE__ */ (0,
|
|
995
|
-
|
|
996
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "mt-1 text-xs text-slate-600", children: [
|
|
997
|
-
"Provider: ",
|
|
998
|
-
voiceTokenResult?.provider ?? voice.provider,
|
|
999
|
-
" \xB7 Model:",
|
|
1000
|
-
" ",
|
|
1001
|
-
voiceTokenResult?.model_id ?? voice.modelId
|
|
1002
|
-
] }),
|
|
1003
|
-
/* @__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." })
|
|
1004
|
-
] }),
|
|
1005
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2", children: [
|
|
1006
|
-
isVoiceActive || isVoiceConnecting ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1007
|
-
"button",
|
|
1008
|
-
{
|
|
1009
|
-
type: "button",
|
|
1010
|
-
className: "rounded-full border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-100",
|
|
1011
|
-
onClick: onStopVoiceInput,
|
|
1012
|
-
disabled: isSubmitting,
|
|
1013
|
-
children: "Stop Voice Input"
|
|
1014
|
-
}
|
|
1015
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1016
|
-
"button",
|
|
1017
|
-
{
|
|
1018
|
-
type: "button",
|
|
1019
|
-
className: "rounded-full bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-slate-800",
|
|
1020
|
-
onClick: onStartVoiceInput,
|
|
1021
|
-
disabled: isSubmitting,
|
|
1022
|
-
children: "Start Voice Input"
|
|
1023
|
-
}
|
|
1024
|
-
),
|
|
1025
|
-
canUseText ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1026
|
-
"button",
|
|
1027
|
-
{
|
|
1028
|
-
type: "button",
|
|
1029
|
-
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",
|
|
1030
|
-
onClick: onAppendTranscript,
|
|
1031
|
-
disabled: isSubmitting || !committedTranscript.trim(),
|
|
1032
|
-
children: "Append Transcript"
|
|
1033
|
-
}
|
|
1034
|
-
) : null
|
|
1035
|
-
] })
|
|
1544
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-4 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
1545
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
1546
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: "Voice Input" }),
|
|
1547
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-1 text-xs text-slate-600", children: "Voice capture loads when selected." })
|
|
1036
1548
|
] }),
|
|
1037
|
-
/* @__PURE__ */ (0,
|
|
1038
|
-
|
|
1549
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex gap-2", children: [
|
|
1550
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1551
|
+
"button",
|
|
1552
|
+
{
|
|
1553
|
+
type: "button",
|
|
1554
|
+
className: "rounded-full bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-slate-800",
|
|
1555
|
+
onClick: onRequestStartVoiceInput,
|
|
1556
|
+
disabled: isSubmitting,
|
|
1557
|
+
children: "Start Voice Input"
|
|
1558
|
+
}
|
|
1559
|
+
),
|
|
1560
|
+
canUseText ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1561
|
+
"button",
|
|
1562
|
+
{
|
|
1563
|
+
type: "button",
|
|
1564
|
+
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",
|
|
1565
|
+
onClick: onRequestAppendTranscript,
|
|
1566
|
+
disabled: isSubmitting,
|
|
1567
|
+
children: "Append Transcript"
|
|
1568
|
+
}
|
|
1569
|
+
) : null
|
|
1570
|
+
] })
|
|
1571
|
+
] }) })
|
|
1572
|
+
] });
|
|
1573
|
+
}
|
|
1574
|
+
function IssueReportVoiceFallback({
|
|
1575
|
+
canUseText,
|
|
1576
|
+
isSubmitting,
|
|
1577
|
+
onSelectText,
|
|
1578
|
+
onSelectVoice,
|
|
1579
|
+
onRequestStartVoiceInput
|
|
1580
|
+
}) {
|
|
1581
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
1582
|
+
canUseText ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-5 flex gap-2", children: [
|
|
1583
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1584
|
+
"button",
|
|
1585
|
+
{
|
|
1586
|
+
type: "button",
|
|
1587
|
+
className: "inline-flex items-center gap-1 rounded-full border border-slate-200 px-3 py-1.5 text-xs font-semibold text-slate-700 transition hover:bg-slate-50",
|
|
1588
|
+
onClick: onSelectText,
|
|
1589
|
+
disabled: isSubmitting,
|
|
1590
|
+
children: [
|
|
1591
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.TextT, { className: "h-3.5 w-3.5" }),
|
|
1592
|
+
"Text Input"
|
|
1593
|
+
]
|
|
1594
|
+
}
|
|
1595
|
+
),
|
|
1596
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1597
|
+
"button",
|
|
1598
|
+
{
|
|
1599
|
+
type: "button",
|
|
1600
|
+
className: "inline-flex items-center gap-1 rounded-full border border-slate-900 bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white transition",
|
|
1601
|
+
onClick: onSelectVoice,
|
|
1602
|
+
disabled: isSubmitting,
|
|
1603
|
+
children: [
|
|
1604
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Microphone, { className: "h-3.5 w-3.5" }),
|
|
1605
|
+
"Voice Input"
|
|
1606
|
+
]
|
|
1607
|
+
}
|
|
1608
|
+
)
|
|
1609
|
+
] }) : null,
|
|
1610
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-4 flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-xs text-slate-600", children: [
|
|
1611
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Spinner, { className: "h-3.5 w-3.5 animate-spin" }),
|
|
1612
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex-1", children: "Loading voice input..." }),
|
|
1613
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1614
|
+
"button",
|
|
1615
|
+
{
|
|
1616
|
+
type: "button",
|
|
1617
|
+
className: "rounded-full bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-slate-800",
|
|
1618
|
+
onClick: onRequestStartVoiceInput,
|
|
1619
|
+
disabled: isSubmitting,
|
|
1620
|
+
children: "Start Voice Input"
|
|
1621
|
+
}
|
|
1622
|
+
)
|
|
1039
1623
|
] })
|
|
1040
1624
|
] });
|
|
1041
1625
|
}
|
|
@@ -1049,13 +1633,13 @@ function IssueReportNoteEditor({
|
|
|
1049
1633
|
onSubmit
|
|
1050
1634
|
}) {
|
|
1051
1635
|
if (!canUseText) {
|
|
1052
|
-
return /* @__PURE__ */ (0,
|
|
1053
|
-
/* @__PURE__ */ (0,
|
|
1054
|
-
/* @__PURE__ */ (0,
|
|
1636
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-5 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-xs text-slate-600", children: [
|
|
1637
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: getIssueNoteLengthMessage(normalizedNote, copy) }),
|
|
1638
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-1", children: "Submit is enabled after a committed transcript reaches 10-2000 characters." })
|
|
1055
1639
|
] });
|
|
1056
1640
|
}
|
|
1057
|
-
return /* @__PURE__ */ (0,
|
|
1058
|
-
/* @__PURE__ */ (0,
|
|
1641
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-5 space-y-2", children: [
|
|
1642
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1059
1643
|
"textarea",
|
|
1060
1644
|
{
|
|
1061
1645
|
value: note,
|
|
@@ -1072,9 +1656,9 @@ function IssueReportNoteEditor({
|
|
|
1072
1656
|
autoFocus: true
|
|
1073
1657
|
}
|
|
1074
1658
|
),
|
|
1075
|
-
/* @__PURE__ */ (0,
|
|
1076
|
-
/* @__PURE__ */ (0,
|
|
1077
|
-
/* @__PURE__ */ (0,
|
|
1659
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between text-xs text-slate-500", children: [
|
|
1660
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: getIssueNoteLengthMessage(note, copy) }),
|
|
1661
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: copy.keyboardShortcutHint })
|
|
1078
1662
|
] })
|
|
1079
1663
|
] });
|
|
1080
1664
|
}
|
|
@@ -1084,10 +1668,10 @@ function IssueReportModalDescription({
|
|
|
1084
1668
|
target
|
|
1085
1669
|
}) {
|
|
1086
1670
|
if (mode === "create" && target) {
|
|
1087
|
-
return /* @__PURE__ */ (0,
|
|
1671
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
1088
1672
|
copy.createDescriptionPrefix,
|
|
1089
1673
|
" ",
|
|
1090
|
-
/* @__PURE__ */ (0,
|
|
1674
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { className: "rounded bg-slate-100 px-1.5 py-0.5 text-xs text-slate-700", children: target.page_url })
|
|
1091
1675
|
] });
|
|
1092
1676
|
}
|
|
1093
1677
|
return mode === "edit" ? copy.editDescription : copy.replyDescription;
|
|
@@ -1106,13 +1690,10 @@ function IssueReportModalBody({
|
|
|
1106
1690
|
normalizedNote,
|
|
1107
1691
|
isValid,
|
|
1108
1692
|
isSubmitting,
|
|
1109
|
-
isVoiceActive,
|
|
1110
|
-
isVoiceConnecting,
|
|
1111
1693
|
voice,
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
scribeError,
|
|
1694
|
+
createVoiceToken,
|
|
1695
|
+
voiceStartRequestId,
|
|
1696
|
+
voiceAppendRequestId,
|
|
1116
1697
|
submitError,
|
|
1117
1698
|
existingAttachments,
|
|
1118
1699
|
removedExistingIds,
|
|
@@ -1123,8 +1704,10 @@ function IssueReportModalBody({
|
|
|
1123
1704
|
onClose,
|
|
1124
1705
|
onSelectText,
|
|
1125
1706
|
onSelectVoice,
|
|
1126
|
-
|
|
1127
|
-
|
|
1707
|
+
onRequestStartVoiceInput,
|
|
1708
|
+
onRequestAppendTranscript,
|
|
1709
|
+
onTranscriptCommitted,
|
|
1710
|
+
onVoiceSubmitMetadataChange,
|
|
1128
1711
|
onAppendTranscript,
|
|
1129
1712
|
onNoteChange,
|
|
1130
1713
|
onSubmit,
|
|
@@ -1133,16 +1716,16 @@ function IssueReportModalBody({
|
|
|
1133
1716
|
onRemovePendingAttachment
|
|
1134
1717
|
}) {
|
|
1135
1718
|
if (isHydrating) {
|
|
1136
|
-
return /* @__PURE__ */ (0,
|
|
1137
|
-
/* @__PURE__ */ (0,
|
|
1138
|
-
/* @__PURE__ */ (0,
|
|
1719
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.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: [
|
|
1720
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Spinner, { className: "h-4 w-4 animate-spin" }),
|
|
1721
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: copy.hydrateLoading })
|
|
1139
1722
|
] });
|
|
1140
1723
|
}
|
|
1141
1724
|
if (error) {
|
|
1142
|
-
return /* @__PURE__ */ (0,
|
|
1143
|
-
/* @__PURE__ */ (0,
|
|
1144
|
-
/* @__PURE__ */ (0,
|
|
1145
|
-
/* @__PURE__ */ (0,
|
|
1725
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.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: [
|
|
1726
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: error }),
|
|
1727
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex gap-2", children: [
|
|
1728
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1146
1729
|
"button",
|
|
1147
1730
|
{
|
|
1148
1731
|
type: "button",
|
|
@@ -1151,7 +1734,7 @@ function IssueReportModalBody({
|
|
|
1151
1734
|
children: copy.retryAction
|
|
1152
1735
|
}
|
|
1153
1736
|
),
|
|
1154
|
-
/* @__PURE__ */ (0,
|
|
1737
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1155
1738
|
"button",
|
|
1156
1739
|
{
|
|
1157
1740
|
type: "button",
|
|
@@ -1163,32 +1746,55 @@ function IssueReportModalBody({
|
|
|
1163
1746
|
] })
|
|
1164
1747
|
] });
|
|
1165
1748
|
}
|
|
1166
|
-
return /* @__PURE__ */ (0,
|
|
1167
|
-
mode === "create" && canUseVoice ? /* @__PURE__ */ (0,
|
|
1168
|
-
|
|
1749
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
1750
|
+
mode === "create" && canUseVoice ? effectiveInputMode === "voice" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1751
|
+
import_react7.default.Suspense,
|
|
1752
|
+
{
|
|
1753
|
+
fallback: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1754
|
+
IssueReportVoiceFallback,
|
|
1755
|
+
{
|
|
1756
|
+
canUseText,
|
|
1757
|
+
isSubmitting,
|
|
1758
|
+
onSelectText,
|
|
1759
|
+
onSelectVoice,
|
|
1760
|
+
onRequestStartVoiceInput
|
|
1761
|
+
}
|
|
1762
|
+
),
|
|
1763
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1764
|
+
VoiceCapture2,
|
|
1765
|
+
{
|
|
1766
|
+
canUseText,
|
|
1767
|
+
effectiveInputMode,
|
|
1768
|
+
isSubmitting,
|
|
1769
|
+
voice,
|
|
1770
|
+
createVoiceToken,
|
|
1771
|
+
startRequestId: voiceStartRequestId,
|
|
1772
|
+
appendRequestId: voiceAppendRequestId,
|
|
1773
|
+
onSelectText,
|
|
1774
|
+
onSelectVoice,
|
|
1775
|
+
onTranscriptCommitted,
|
|
1776
|
+
onSubmitMetadataChange: onVoiceSubmitMetadataChange,
|
|
1777
|
+
onAppendTranscript
|
|
1778
|
+
}
|
|
1779
|
+
)
|
|
1780
|
+
}
|
|
1781
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1782
|
+
IssueReportVoiceActivationPanel,
|
|
1169
1783
|
{
|
|
1170
1784
|
canUseText,
|
|
1171
1785
|
effectiveInputMode,
|
|
1172
1786
|
isSubmitting,
|
|
1173
|
-
isVoiceActive,
|
|
1174
|
-
isVoiceConnecting,
|
|
1175
|
-
voice,
|
|
1176
|
-
voiceTokenResult,
|
|
1177
|
-
committedTranscript,
|
|
1178
|
-
voiceError,
|
|
1179
|
-
scribeError,
|
|
1180
1787
|
onSelectText,
|
|
1181
1788
|
onSelectVoice,
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
onAppendTranscript
|
|
1789
|
+
onRequestStartVoiceInput,
|
|
1790
|
+
onRequestAppendTranscript
|
|
1185
1791
|
}
|
|
1186
1792
|
) : null,
|
|
1187
|
-
mode === "reply" && issue ? /* @__PURE__ */ (0,
|
|
1188
|
-
/* @__PURE__ */ (0,
|
|
1189
|
-
/* @__PURE__ */ (0,
|
|
1793
|
+
mode === "reply" && issue ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-5 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: [
|
|
1794
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: copy.originalIssueLabel }),
|
|
1795
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-1 text-sm text-slate-700", children: issue.note })
|
|
1190
1796
|
] }) : null,
|
|
1191
|
-
/* @__PURE__ */ (0,
|
|
1797
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1192
1798
|
IssueReportNoteEditor,
|
|
1193
1799
|
{
|
|
1194
1800
|
canUseText,
|
|
@@ -1200,7 +1806,7 @@ function IssueReportModalBody({
|
|
|
1200
1806
|
onSubmit
|
|
1201
1807
|
}
|
|
1202
1808
|
),
|
|
1203
|
-
canUseAttachments && /* @__PURE__ */ (0,
|
|
1809
|
+
canUseAttachments && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1204
1810
|
AttachmentPicker,
|
|
1205
1811
|
{
|
|
1206
1812
|
existingAttachments,
|
|
@@ -1214,9 +1820,9 @@ function IssueReportModalBody({
|
|
|
1214
1820
|
onRemovePending: onRemovePendingAttachment
|
|
1215
1821
|
}
|
|
1216
1822
|
),
|
|
1217
|
-
submitError ? /* @__PURE__ */ (0,
|
|
1218
|
-
/* @__PURE__ */ (0,
|
|
1219
|
-
/* @__PURE__ */ (0,
|
|
1823
|
+
submitError ? /* @__PURE__ */ (0, import_jsx_runtime3.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,
|
|
1824
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-6 flex justify-end gap-3", children: [
|
|
1825
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1220
1826
|
"button",
|
|
1221
1827
|
{
|
|
1222
1828
|
type: "button",
|
|
@@ -1226,11 +1832,11 @@ function IssueReportModalBody({
|
|
|
1226
1832
|
children: copy.cancelAction
|
|
1227
1833
|
}
|
|
1228
1834
|
),
|
|
1229
|
-
/* @__PURE__ */ (0,
|
|
1835
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1230
1836
|
"button",
|
|
1231
1837
|
{
|
|
1232
1838
|
type: "button",
|
|
1233
|
-
className:
|
|
1839
|
+
className: cn2(
|
|
1234
1840
|
"rounded-full px-4 py-2 text-sm font-semibold text-white transition",
|
|
1235
1841
|
isValid && !isSubmitting ? "bg-slate-900 hover:bg-slate-800" : "cursor-not-allowed bg-slate-300"
|
|
1236
1842
|
),
|
|
@@ -1240,104 +1846,9 @@ function IssueReportModalBody({
|
|
|
1240
1846
|
}
|
|
1241
1847
|
)
|
|
1242
1848
|
] }),
|
|
1243
|
-
mode !== "create" && issue ? /* @__PURE__ */ (0,
|
|
1849
|
+
mode !== "create" && issue ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IssueReportMessageThread, { issueReportId: issue.id }) : null
|
|
1244
1850
|
] });
|
|
1245
1851
|
}
|
|
1246
|
-
function useIssueReportVoiceCapture({
|
|
1247
|
-
canUseVoice,
|
|
1248
|
-
defaultInputMode,
|
|
1249
|
-
isSubmitting,
|
|
1250
|
-
voice
|
|
1251
|
-
}) {
|
|
1252
|
-
const [inputMode, setInputMode] = (0, import_react5.useState)(defaultInputMode);
|
|
1253
|
-
const [voiceTokenResult, setVoiceTokenResult] = (0, import_react5.useState)(null);
|
|
1254
|
-
const [voiceSubmitMetadata, setVoiceSubmitMetadata] = (0, import_react5.useState)(null);
|
|
1255
|
-
const [voiceError, setVoiceError] = (0, import_react5.useState)(null);
|
|
1256
|
-
const {
|
|
1257
|
-
status: scribeStatus,
|
|
1258
|
-
isConnected,
|
|
1259
|
-
isTranscribing,
|
|
1260
|
-
committedTranscripts,
|
|
1261
|
-
error: scribeError,
|
|
1262
|
-
connect: connectScribe,
|
|
1263
|
-
disconnect: disconnectScribe
|
|
1264
|
-
} = (0, import_react3.useScribe)({
|
|
1265
|
-
autoConnect: false,
|
|
1266
|
-
modelId: voice.modelId,
|
|
1267
|
-
microphone: voice.microphone
|
|
1268
|
-
});
|
|
1269
|
-
const committedTranscript = (0, import_react5.useMemo)(
|
|
1270
|
-
() => getCommittedTranscriptText(committedTranscripts),
|
|
1271
|
-
[committedTranscripts]
|
|
1272
|
-
);
|
|
1273
|
-
const isVoiceConnecting = scribeStatus === "connecting";
|
|
1274
|
-
const isVoiceActive = isConnected || isTranscribing;
|
|
1275
|
-
const resetVoiceCapture = (0, import_react5.useCallback)(() => {
|
|
1276
|
-
disconnectScribe();
|
|
1277
|
-
setInputMode(defaultInputMode);
|
|
1278
|
-
setVoiceTokenResult(null);
|
|
1279
|
-
setVoiceSubmitMetadata(null);
|
|
1280
|
-
setVoiceError(null);
|
|
1281
|
-
}, [defaultInputMode, disconnectScribe]);
|
|
1282
|
-
const startVoiceInput = (0, import_react5.useCallback)(
|
|
1283
|
-
async (createVoiceToken) => {
|
|
1284
|
-
if (!canUseVoice || isSubmitting || isVoiceActive || isVoiceConnecting) {
|
|
1285
|
-
return;
|
|
1286
|
-
}
|
|
1287
|
-
if (!createVoiceToken) {
|
|
1288
|
-
setVoiceError("Voice input is unavailable for this client.");
|
|
1289
|
-
return;
|
|
1290
|
-
}
|
|
1291
|
-
setVoiceError(null);
|
|
1292
|
-
try {
|
|
1293
|
-
const tokenResult = await createVoiceToken();
|
|
1294
|
-
setVoiceTokenResult(tokenResult);
|
|
1295
|
-
setVoiceSubmitMetadata(tokenResult);
|
|
1296
|
-
await connectScribe({
|
|
1297
|
-
token: tokenResult.token,
|
|
1298
|
-
modelId: tokenResult.model_id,
|
|
1299
|
-
microphone: voice.microphone
|
|
1300
|
-
});
|
|
1301
|
-
setInputMode("voice");
|
|
1302
|
-
} catch (startError) {
|
|
1303
|
-
setVoiceError(resolveErrorMessage(startError, "Failed to start voice input."));
|
|
1304
|
-
}
|
|
1305
|
-
},
|
|
1306
|
-
[
|
|
1307
|
-
canUseVoice,
|
|
1308
|
-
connectScribe,
|
|
1309
|
-
isSubmitting,
|
|
1310
|
-
isVoiceActive,
|
|
1311
|
-
isVoiceConnecting,
|
|
1312
|
-
voice.microphone
|
|
1313
|
-
]
|
|
1314
|
-
);
|
|
1315
|
-
const appendTranscript = (0, import_react5.useCallback)(
|
|
1316
|
-
(setNote) => {
|
|
1317
|
-
if (!committedTranscript.trim()) {
|
|
1318
|
-
return;
|
|
1319
|
-
}
|
|
1320
|
-
setNote((current) => appendTranscriptToNote(current, committedTranscript));
|
|
1321
|
-
setInputMode("text");
|
|
1322
|
-
},
|
|
1323
|
-
[committedTranscript]
|
|
1324
|
-
);
|
|
1325
|
-
return {
|
|
1326
|
-
inputMode,
|
|
1327
|
-
setInputMode,
|
|
1328
|
-
voiceTokenResult,
|
|
1329
|
-
voiceSubmitMetadata,
|
|
1330
|
-
committedTranscript,
|
|
1331
|
-
voiceError,
|
|
1332
|
-
scribeError,
|
|
1333
|
-
isVoiceConnecting,
|
|
1334
|
-
isVoiceActive,
|
|
1335
|
-
resetVoiceCapture,
|
|
1336
|
-
startVoiceInput,
|
|
1337
|
-
stopVoiceInput: disconnectScribe,
|
|
1338
|
-
appendTranscript
|
|
1339
|
-
};
|
|
1340
|
-
}
|
|
1341
1852
|
var INITIAL_UPLOAD_PROGRESS = {
|
|
1342
1853
|
phase: "idle",
|
|
1343
1854
|
uploaded: 0,
|
|
@@ -1373,23 +1884,23 @@ function AttachmentPicker({
|
|
|
1373
1884
|
onRemoveExisting,
|
|
1374
1885
|
onRemovePending
|
|
1375
1886
|
}) {
|
|
1376
|
-
const fileInputRef = (0,
|
|
1887
|
+
const fileInputRef = (0, import_react7.useRef)(null);
|
|
1377
1888
|
const retainedExisting = existingAttachments.filter(
|
|
1378
1889
|
(a) => !removedExistingIds.has(a.id)
|
|
1379
1890
|
);
|
|
1380
1891
|
const totalCount = retainedExisting.length + pendingFiles.length;
|
|
1381
1892
|
const canAdd = totalCount < ATTACHMENT_MAX_COUNT && !disabled;
|
|
1382
|
-
return /* @__PURE__ */ (0,
|
|
1383
|
-
/* @__PURE__ */ (0,
|
|
1384
|
-
/* @__PURE__ */ (0,
|
|
1385
|
-
/* @__PURE__ */ (0,
|
|
1893
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-4 space-y-3", children: [
|
|
1894
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between", children: [
|
|
1895
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-1.5 text-xs font-medium uppercase tracking-wide text-slate-500", children: [
|
|
1896
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Image, { className: "h-3.5 w-3.5" }),
|
|
1386
1897
|
"Screenshots (",
|
|
1387
1898
|
totalCount,
|
|
1388
1899
|
"/",
|
|
1389
1900
|
ATTACHMENT_MAX_COUNT,
|
|
1390
1901
|
")"
|
|
1391
1902
|
] }),
|
|
1392
|
-
/* @__PURE__ */ (0,
|
|
1903
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1393
1904
|
"input",
|
|
1394
1905
|
{
|
|
1395
1906
|
ref: fileInputRef,
|
|
@@ -1407,11 +1918,11 @@ function AttachmentPicker({
|
|
|
1407
1918
|
"aria-label": "Select screenshot files"
|
|
1408
1919
|
}
|
|
1409
1920
|
),
|
|
1410
|
-
/* @__PURE__ */ (0,
|
|
1921
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1411
1922
|
"button",
|
|
1412
1923
|
{
|
|
1413
1924
|
type: "button",
|
|
1414
|
-
className:
|
|
1925
|
+
className: cn2(
|
|
1415
1926
|
"rounded-full border px-3 py-1 text-xs font-medium transition",
|
|
1416
1927
|
canAdd ? "border-slate-300 text-slate-700 hover:bg-slate-50" : "cursor-not-allowed border-slate-200 text-slate-400"
|
|
1417
1928
|
),
|
|
@@ -1422,7 +1933,7 @@ function AttachmentPicker({
|
|
|
1422
1933
|
}
|
|
1423
1934
|
)
|
|
1424
1935
|
] }),
|
|
1425
|
-
validationErrors.length > 0 && /* @__PURE__ */ (0,
|
|
1936
|
+
validationErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "space-y-1", children: validationErrors.map((err, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1426
1937
|
"div",
|
|
1427
1938
|
{
|
|
1428
1939
|
className: "rounded-xl border border-rose-200 bg-rose-50 px-3 py-2 text-xs text-rose-700",
|
|
@@ -1431,15 +1942,15 @@ function AttachmentPicker({
|
|
|
1431
1942
|
},
|
|
1432
1943
|
i
|
|
1433
1944
|
)) }),
|
|
1434
|
-
uploadProgress.phase === "uploading" && /* @__PURE__ */ (0,
|
|
1435
|
-
/* @__PURE__ */ (0,
|
|
1945
|
+
uploadProgress.phase === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2 rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-xs text-slate-600", children: [
|
|
1946
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Spinner, { className: "h-3.5 w-3.5 animate-spin" }),
|
|
1436
1947
|
"Uploading ",
|
|
1437
1948
|
uploadProgress.uploaded,
|
|
1438
1949
|
" of ",
|
|
1439
1950
|
uploadProgress.total,
|
|
1440
1951
|
"..."
|
|
1441
1952
|
] }),
|
|
1442
|
-
uploadProgress.phase === "error" && uploadProgress.error && /* @__PURE__ */ (0,
|
|
1953
|
+
uploadProgress.phase === "error" && uploadProgress.error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1443
1954
|
"div",
|
|
1444
1955
|
{
|
|
1445
1956
|
className: "rounded-xl border border-rose-200 bg-rose-50 px-3 py-2 text-xs text-rose-700",
|
|
@@ -1447,18 +1958,18 @@ function AttachmentPicker({
|
|
|
1447
1958
|
children: uploadProgress.error
|
|
1448
1959
|
}
|
|
1449
1960
|
),
|
|
1450
|
-
(retainedExisting.length > 0 || pendingFiles.length > 0) && /* @__PURE__ */ (0,
|
|
1451
|
-
retainedExisting.map((att) => /* @__PURE__ */ (0,
|
|
1961
|
+
(retainedExisting.length > 0 || pendingFiles.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap gap-2", children: [
|
|
1962
|
+
retainedExisting.map((att) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1452
1963
|
"div",
|
|
1453
1964
|
{
|
|
1454
1965
|
className: "group relative flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-3 py-2",
|
|
1455
1966
|
children: [
|
|
1456
|
-
/* @__PURE__ */ (0,
|
|
1457
|
-
/* @__PURE__ */ (0,
|
|
1458
|
-
/* @__PURE__ */ (0,
|
|
1459
|
-
/* @__PURE__ */ (0,
|
|
1967
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Image, { className: "h-4 w-4 flex-shrink-0 text-slate-400" }),
|
|
1968
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "min-w-0", children: [
|
|
1969
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "max-w-[140px] truncate text-xs font-medium text-slate-700", children: att.original_filename }),
|
|
1970
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-xs text-slate-400", children: formatFileSize(att.byte_size) })
|
|
1460
1971
|
] }),
|
|
1461
|
-
/* @__PURE__ */ (0,
|
|
1972
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1462
1973
|
"button",
|
|
1463
1974
|
{
|
|
1464
1975
|
type: "button",
|
|
@@ -1466,19 +1977,19 @@ function AttachmentPicker({
|
|
|
1466
1977
|
onClick: () => onRemoveExisting(att.id),
|
|
1467
1978
|
disabled,
|
|
1468
1979
|
"aria-label": `Remove ${att.original_filename}`,
|
|
1469
|
-
children: /* @__PURE__ */ (0,
|
|
1980
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Trash, { className: "h-3.5 w-3.5" })
|
|
1470
1981
|
}
|
|
1471
1982
|
)
|
|
1472
1983
|
]
|
|
1473
1984
|
},
|
|
1474
1985
|
att.id
|
|
1475
1986
|
)),
|
|
1476
|
-
pendingFiles.map((pf) => /* @__PURE__ */ (0,
|
|
1987
|
+
pendingFiles.map((pf) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1477
1988
|
"div",
|
|
1478
1989
|
{
|
|
1479
1990
|
className: "group relative flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-3 py-2",
|
|
1480
1991
|
children: [
|
|
1481
|
-
/* @__PURE__ */ (0,
|
|
1992
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1482
1993
|
"img",
|
|
1483
1994
|
{
|
|
1484
1995
|
src: pf.previewUrl,
|
|
@@ -1486,11 +1997,11 @@ function AttachmentPicker({
|
|
|
1486
1997
|
className: "h-8 w-8 flex-shrink-0 rounded object-cover"
|
|
1487
1998
|
}
|
|
1488
1999
|
),
|
|
1489
|
-
/* @__PURE__ */ (0,
|
|
1490
|
-
/* @__PURE__ */ (0,
|
|
1491
|
-
/* @__PURE__ */ (0,
|
|
2000
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "min-w-0", children: [
|
|
2001
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "max-w-[140px] truncate text-xs font-medium text-slate-700", children: pf.file.name }),
|
|
2002
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-xs text-slate-400", children: formatFileSize(pf.file.size) })
|
|
1492
2003
|
] }),
|
|
1493
|
-
/* @__PURE__ */ (0,
|
|
2004
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1494
2005
|
"button",
|
|
1495
2006
|
{
|
|
1496
2007
|
type: "button",
|
|
@@ -1498,7 +2009,7 @@ function AttachmentPicker({
|
|
|
1498
2009
|
onClick: () => onRemovePending(pf.clientId),
|
|
1499
2010
|
disabled,
|
|
1500
2011
|
"aria-label": `Remove ${pf.file.name}`,
|
|
1501
|
-
children: /* @__PURE__ */ (0,
|
|
2012
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Trash, { className: "h-3.5 w-3.5" })
|
|
1502
2013
|
}
|
|
1503
2014
|
)
|
|
1504
2015
|
]
|
|
@@ -1509,16 +2020,16 @@ function AttachmentPicker({
|
|
|
1509
2020
|
] });
|
|
1510
2021
|
}
|
|
1511
2022
|
function useAttachmentState(existingAttachments) {
|
|
1512
|
-
const [pendingFiles, setPendingFiles] = (0,
|
|
1513
|
-
const [removedExistingIds, setRemovedExistingIds] = (0,
|
|
2023
|
+
const [pendingFiles, setPendingFiles] = (0, import_react7.useState)([]);
|
|
2024
|
+
const [removedExistingIds, setRemovedExistingIds] = (0, import_react7.useState)(
|
|
1514
2025
|
/* @__PURE__ */ new Set()
|
|
1515
2026
|
);
|
|
1516
|
-
const [validationErrors, setValidationErrors] = (0,
|
|
1517
|
-
const [uploadProgress, setUploadProgress] = (0,
|
|
2027
|
+
const [validationErrors, setValidationErrors] = (0, import_react7.useState)([]);
|
|
2028
|
+
const [uploadProgress, setUploadProgress] = (0, import_react7.useState)(INITIAL_UPLOAD_PROGRESS);
|
|
1518
2029
|
const retainedExistingCount = existingAttachments.filter(
|
|
1519
2030
|
(a) => !removedExistingIds.has(a.id)
|
|
1520
2031
|
).length;
|
|
1521
|
-
const reset = (0,
|
|
2032
|
+
const reset = (0, import_react7.useCallback)(() => {
|
|
1522
2033
|
for (const pf of pendingFiles) {
|
|
1523
2034
|
URL.revokeObjectURL(pf.previewUrl);
|
|
1524
2035
|
}
|
|
@@ -1527,7 +2038,7 @@ function useAttachmentState(existingAttachments) {
|
|
|
1527
2038
|
setValidationErrors([]);
|
|
1528
2039
|
setUploadProgress(INITIAL_UPLOAD_PROGRESS);
|
|
1529
2040
|
}, [pendingFiles]);
|
|
1530
|
-
const addFiles = (0,
|
|
2041
|
+
const addFiles = (0, import_react7.useCallback)(
|
|
1531
2042
|
(fileList) => {
|
|
1532
2043
|
const currentTotal = retainedExistingCount + pendingFiles.length;
|
|
1533
2044
|
const errors = [];
|
|
@@ -1561,10 +2072,10 @@ function useAttachmentState(existingAttachments) {
|
|
|
1561
2072
|
},
|
|
1562
2073
|
[pendingFiles.length, retainedExistingCount]
|
|
1563
2074
|
);
|
|
1564
|
-
const removeExisting = (0,
|
|
2075
|
+
const removeExisting = (0, import_react7.useCallback)((id) => {
|
|
1565
2076
|
setRemovedExistingIds((prev) => /* @__PURE__ */ new Set([...prev, id]));
|
|
1566
2077
|
}, []);
|
|
1567
|
-
const removePending = (0,
|
|
2078
|
+
const removePending = (0, import_react7.useCallback)((clientId) => {
|
|
1568
2079
|
setPendingFiles((prev) => {
|
|
1569
2080
|
const removed = prev.find((pf) => pf.clientId === clientId);
|
|
1570
2081
|
if (removed) {
|
|
@@ -1574,7 +2085,7 @@ function useAttachmentState(existingAttachments) {
|
|
|
1574
2085
|
});
|
|
1575
2086
|
setUploadProgress(INITIAL_UPLOAD_PROGRESS);
|
|
1576
2087
|
}, []);
|
|
1577
|
-
const markPendingUploaded = (0,
|
|
2088
|
+
const markPendingUploaded = (0, import_react7.useCallback)((clientId, attachmentId) => {
|
|
1578
2089
|
setPendingFiles(
|
|
1579
2090
|
(prev) => prev.map(
|
|
1580
2091
|
(pf) => pf.clientId === clientId ? { ...pf, uploadedAttachmentId: attachmentId } : pf
|
|
@@ -1632,19 +2143,19 @@ function MessageBubble({
|
|
|
1632
2143
|
const isReporter = message.actor.author_type === "reporter";
|
|
1633
2144
|
const isFinal = message.kind === "final_response";
|
|
1634
2145
|
const authorLabel = isReporter ? copy.threadAuthorReporter : copy.threadAuthorOperator;
|
|
1635
|
-
return /* @__PURE__ */ (0,
|
|
2146
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1636
2147
|
"li",
|
|
1637
2148
|
{
|
|
1638
|
-
className:
|
|
2149
|
+
className: cn2(
|
|
1639
2150
|
"flex flex-col gap-1",
|
|
1640
2151
|
isReporter ? "items-end" : "items-start"
|
|
1641
2152
|
),
|
|
1642
2153
|
children: [
|
|
1643
|
-
/* @__PURE__ */ (0,
|
|
1644
|
-
/* @__PURE__ */ (0,
|
|
2154
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2155
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1645
2156
|
"span",
|
|
1646
2157
|
{
|
|
1647
|
-
className:
|
|
2158
|
+
className: cn2(
|
|
1648
2159
|
"rounded-full px-2 py-0.5 font-medium",
|
|
1649
2160
|
BADGE_TEXT,
|
|
1650
2161
|
getMessageKindClassName(message.kind)
|
|
@@ -1652,8 +2163,8 @@ function MessageBubble({
|
|
|
1652
2163
|
children: getMessageKindLabel(message.kind, copy)
|
|
1653
2164
|
}
|
|
1654
2165
|
),
|
|
1655
|
-
/* @__PURE__ */ (0,
|
|
1656
|
-
/* @__PURE__ */ (0,
|
|
2166
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: cn2("font-medium text-slate-600", LABEL_TEXT), children: authorLabel }),
|
|
2167
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1657
2168
|
"time",
|
|
1658
2169
|
{
|
|
1659
2170
|
className: "text-xs text-slate-400",
|
|
@@ -1662,10 +2173,10 @@ function MessageBubble({
|
|
|
1662
2173
|
}
|
|
1663
2174
|
)
|
|
1664
2175
|
] }),
|
|
1665
|
-
/* @__PURE__ */ (0,
|
|
2176
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1666
2177
|
"div",
|
|
1667
2178
|
{
|
|
1668
|
-
className:
|
|
2179
|
+
className: cn2(
|
|
1669
2180
|
"max-w-[85%] whitespace-pre-wrap rounded-2xl border px-3 py-2 text-sm",
|
|
1670
2181
|
isReporter ? "border-slate-900 bg-slate-900 text-white" : isFinal ? "border-emerald-200 bg-emerald-50 text-emerald-950" : "border-slate-200 bg-white text-slate-800"
|
|
1671
2182
|
),
|
|
@@ -1681,9 +2192,9 @@ function ReporterResponseComposer({
|
|
|
1681
2192
|
copy
|
|
1682
2193
|
}) {
|
|
1683
2194
|
const mutation = useIssueReportingMessageMutation(issueReportId);
|
|
1684
|
-
const idempotencyKeyRef = (0,
|
|
1685
|
-
const [body, setBody] = (0,
|
|
1686
|
-
const [submitError, setSubmitError] = (0,
|
|
2195
|
+
const idempotencyKeyRef = (0, import_react7.useRef)(generateIdempotencyKey());
|
|
2196
|
+
const [body, setBody] = (0, import_react7.useState)("");
|
|
2197
|
+
const [submitError, setSubmitError] = (0, import_react7.useState)(null);
|
|
1687
2198
|
const normalized = body.trim();
|
|
1688
2199
|
const isValid = normalized.length >= REPORTER_MESSAGE_MIN_LENGTH && normalized.length <= REPORTER_MESSAGE_MAX_LENGTH;
|
|
1689
2200
|
const isSubmitting = mutation.isPending;
|
|
@@ -1705,22 +2216,22 @@ function ReporterResponseComposer({
|
|
|
1705
2216
|
idempotencyKeyRef.current = generateIdempotencyKey();
|
|
1706
2217
|
return;
|
|
1707
2218
|
}
|
|
1708
|
-
setSubmitError(
|
|
2219
|
+
setSubmitError(resolveErrorMessage2(error, copy.threadResponseFailed));
|
|
1709
2220
|
}
|
|
1710
2221
|
};
|
|
1711
|
-
return /* @__PURE__ */ (0,
|
|
1712
|
-
/* @__PURE__ */ (0,
|
|
2222
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-3 space-y-2", children: [
|
|
2223
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1713
2224
|
"label",
|
|
1714
2225
|
{
|
|
1715
2226
|
htmlFor: "issue-report-thread-response",
|
|
1716
|
-
className:
|
|
2227
|
+
className: cn2(
|
|
1717
2228
|
"block font-medium uppercase tracking-wide text-slate-500",
|
|
1718
2229
|
LABEL_TEXT
|
|
1719
2230
|
),
|
|
1720
2231
|
children: copy.threadResponseLabel
|
|
1721
2232
|
}
|
|
1722
2233
|
),
|
|
1723
|
-
/* @__PURE__ */ (0,
|
|
2234
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1724
2235
|
"textarea",
|
|
1725
2236
|
{
|
|
1726
2237
|
id: "issue-report-thread-response",
|
|
@@ -1737,7 +2248,7 @@ function ReporterResponseComposer({
|
|
|
1737
2248
|
disabled: isSubmitting
|
|
1738
2249
|
}
|
|
1739
2250
|
),
|
|
1740
|
-
submitError ? /* @__PURE__ */ (0,
|
|
2251
|
+
submitError ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1741
2252
|
"div",
|
|
1742
2253
|
{
|
|
1743
2254
|
className: "rounded-xl border border-rose-200 bg-rose-50 px-3 py-2 text-xs text-rose-700",
|
|
@@ -1745,11 +2256,11 @@ function ReporterResponseComposer({
|
|
|
1745
2256
|
children: submitError
|
|
1746
2257
|
}
|
|
1747
2258
|
) : null,
|
|
1748
|
-
/* @__PURE__ */ (0,
|
|
2259
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1749
2260
|
"button",
|
|
1750
2261
|
{
|
|
1751
2262
|
type: "button",
|
|
1752
|
-
className:
|
|
2263
|
+
className: cn2(
|
|
1753
2264
|
"rounded-full px-4 py-2 text-sm font-semibold text-white transition",
|
|
1754
2265
|
isValid && !isSubmitting ? "bg-slate-900 hover:bg-slate-800" : "cursor-not-allowed bg-slate-300"
|
|
1755
2266
|
),
|
|
@@ -1768,7 +2279,7 @@ function IssueReportMessageThread({
|
|
|
1768
2279
|
const supportsSubmit = Boolean(client.issueReporting.submitMessage);
|
|
1769
2280
|
const query = useIssueReportingMessages(issueReportId);
|
|
1770
2281
|
const needsResponse = query.data?.needs_response ?? false;
|
|
1771
|
-
(0,
|
|
2282
|
+
(0, import_react7.useEffect)(() => {
|
|
1772
2283
|
setNeedsResponse(issueReportId, needsResponse);
|
|
1773
2284
|
return () => {
|
|
1774
2285
|
setNeedsResponse(issueReportId, false);
|
|
@@ -1778,18 +2289,18 @@ function IssueReportMessageThread({
|
|
|
1778
2289
|
return null;
|
|
1779
2290
|
}
|
|
1780
2291
|
const visibleMessages = selectReporterVisibleMessages(query.data?.items ?? []);
|
|
1781
|
-
return /* @__PURE__ */ (0,
|
|
2292
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1782
2293
|
"section",
|
|
1783
2294
|
{
|
|
1784
2295
|
"aria-label": copy.threadTitle,
|
|
1785
2296
|
className: "mt-6 border-t border-slate-100 pt-5",
|
|
1786
2297
|
children: [
|
|
1787
|
-
/* @__PURE__ */ (0,
|
|
1788
|
-
/* @__PURE__ */ (0,
|
|
1789
|
-
needsResponse ? /* @__PURE__ */ (0,
|
|
2298
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
|
|
2299
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "text-sm font-semibold text-slate-900", children: copy.threadTitle }),
|
|
2300
|
+
needsResponse ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1790
2301
|
"span",
|
|
1791
2302
|
{
|
|
1792
|
-
className:
|
|
2303
|
+
className: cn2(
|
|
1793
2304
|
"rounded-full px-2 py-0.5 font-medium",
|
|
1794
2305
|
BADGE_TEXT,
|
|
1795
2306
|
"bg-amber-100 text-amber-700"
|
|
@@ -1798,13 +2309,13 @@ function IssueReportMessageThread({
|
|
|
1798
2309
|
}
|
|
1799
2310
|
) : null
|
|
1800
2311
|
] }),
|
|
1801
|
-
/* @__PURE__ */ (0,
|
|
1802
|
-
query.isPending ? /* @__PURE__ */ (0,
|
|
1803
|
-
/* @__PURE__ */ (0,
|
|
1804
|
-
/* @__PURE__ */ (0,
|
|
1805
|
-
] }) : query.error ? /* @__PURE__ */ (0,
|
|
1806
|
-
/* @__PURE__ */ (0,
|
|
1807
|
-
/* @__PURE__ */ (0,
|
|
2312
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-1 text-xs text-slate-500", children: copy.threadDescription }),
|
|
2313
|
+
query.isPending ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-4 flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-sm text-slate-600", children: [
|
|
2314
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Spinner, { className: "h-4 w-4 animate-spin" }),
|
|
2315
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: copy.threadLoading })
|
|
2316
|
+
] }) : query.error ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-4 space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
|
|
2317
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: resolveErrorMessage2(query.error, copy.threadLoadFailed) }),
|
|
2318
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1808
2319
|
"button",
|
|
1809
2320
|
{
|
|
1810
2321
|
type: "button",
|
|
@@ -1813,8 +2324,8 @@ function IssueReportMessageThread({
|
|
|
1813
2324
|
children: copy.retryAction
|
|
1814
2325
|
}
|
|
1815
2326
|
)
|
|
1816
|
-
] }) : visibleMessages.length === 0 ? /* @__PURE__ */ (0,
|
|
1817
|
-
supportsSubmit ? /* @__PURE__ */ (0,
|
|
2327
|
+
] }) : visibleMessages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-4 rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-4 py-6 text-center text-sm text-slate-500", children: copy.threadEmpty }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { className: "mt-4 space-y-3", children: visibleMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MessageBubble, { message, copy }, message.id)) }),
|
|
2328
|
+
supportsSubmit ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1818
2329
|
ReporterResponseComposer,
|
|
1819
2330
|
{
|
|
1820
2331
|
issueReportId,
|
|
@@ -1831,10 +2342,10 @@ function IssueReportModeBanner() {
|
|
|
1831
2342
|
if (!reportMode?.isReportMode) {
|
|
1832
2343
|
return null;
|
|
1833
2344
|
}
|
|
1834
|
-
return /* @__PURE__ */ (0,
|
|
1835
|
-
/* @__PURE__ */ (0,
|
|
1836
|
-
/* @__PURE__ */ (0,
|
|
1837
|
-
/* @__PURE__ */ (0,
|
|
2345
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: cn2("fixed inset-x-4 top-4 flex justify-center", Z_BANNER), children: /* @__PURE__ */ (0, import_jsx_runtime3.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_runtime3.jsxs)("div", { className: "flex flex-wrap items-center justify-center gap-3", children: [
|
|
2346
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "font-medium", children: copy.reportModeTitle }),
|
|
2347
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-amber-900/80", children: copy.reportModeDescription }),
|
|
2348
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1838
2349
|
"button",
|
|
1839
2350
|
{
|
|
1840
2351
|
type: "button",
|
|
@@ -1853,15 +2364,15 @@ function IssueList({
|
|
|
1853
2364
|
const { copy, principalId } = useIssueReporting();
|
|
1854
2365
|
const history = useIssueReportingHistory(filter);
|
|
1855
2366
|
if (history.isPending) {
|
|
1856
|
-
return /* @__PURE__ */ (0,
|
|
1857
|
-
/* @__PURE__ */ (0,
|
|
1858
|
-
/* @__PURE__ */ (0,
|
|
2367
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.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: [
|
|
2368
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Spinner, { className: "h-4 w-4 animate-spin" }),
|
|
2369
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: copy.historyLoading })
|
|
1859
2370
|
] });
|
|
1860
2371
|
}
|
|
1861
2372
|
if (history.error) {
|
|
1862
|
-
return /* @__PURE__ */ (0,
|
|
1863
|
-
/* @__PURE__ */ (0,
|
|
1864
|
-
/* @__PURE__ */ (0,
|
|
2373
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.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: [
|
|
2374
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: history.error.message || copy.historyLoadFailed }),
|
|
2375
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1865
2376
|
"button",
|
|
1866
2377
|
{
|
|
1867
2378
|
type: "button",
|
|
@@ -1874,26 +2385,26 @@ function IssueList({
|
|
|
1874
2385
|
}
|
|
1875
2386
|
if (history.items.length === 0) {
|
|
1876
2387
|
const emptyMessage = filter === "unresolved" ? copy.emptyUnresolved : filter === "resolved" ? copy.emptyResolved : copy.emptyAll;
|
|
1877
|
-
return /* @__PURE__ */ (0,
|
|
2388
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-4 py-8 text-center text-sm text-slate-500", children: emptyMessage });
|
|
1878
2389
|
}
|
|
1879
|
-
return /* @__PURE__ */ (0,
|
|
2390
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "max-h-80 space-y-2 overflow-y-auto pr-1", children: history.items.map((issue) => {
|
|
1880
2391
|
const action = getIssueAction(issue, principalId);
|
|
1881
2392
|
const onAction = action === "edit" ? () => onEdit(issue.id) : action === "reply" ? () => onReply(issue.id) : null;
|
|
1882
|
-
return /* @__PURE__ */ (0,
|
|
2393
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1883
2394
|
"div",
|
|
1884
2395
|
{
|
|
1885
2396
|
className: "rounded-2xl border border-slate-200 bg-white px-3 py-3 shadow-sm",
|
|
1886
|
-
children: /* @__PURE__ */ (0,
|
|
1887
|
-
/* @__PURE__ */ (0,
|
|
1888
|
-
/* @__PURE__ */ (0,
|
|
1889
|
-
/* @__PURE__ */ (0,
|
|
1890
|
-
/* @__PURE__ */ (0,
|
|
1891
|
-
/* @__PURE__ */ (0,
|
|
1892
|
-
/* @__PURE__ */ (0,
|
|
1893
|
-
/* @__PURE__ */ (0,
|
|
2397
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-start gap-3", children: [
|
|
2398
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-0.5 flex-shrink-0", children: isClosedIssueStatus(issue.status) ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.CheckCircle, { className: "h-4 w-4 text-emerald-600", weight: "fill" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Circle, { className: "h-4 w-4 text-rose-600", weight: "fill" }) }),
|
|
2399
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
2400
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [
|
|
2401
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "min-w-0", children: [
|
|
2402
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "truncate text-sm font-semibold text-slate-900", children: issue.target.component_label }),
|
|
2403
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-1 flex flex-wrap items-center gap-2", children: [
|
|
2404
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1894
2405
|
"span",
|
|
1895
2406
|
{
|
|
1896
|
-
className:
|
|
2407
|
+
className: cn2(
|
|
1897
2408
|
"rounded-full px-2 py-0.5 font-medium",
|
|
1898
2409
|
BADGE_TEXT,
|
|
1899
2410
|
getIssueStatusClassName(issue.status)
|
|
@@ -1901,11 +2412,11 @@ function IssueList({
|
|
|
1901
2412
|
children: getIssueStatusBadgeLabel(issue.status)
|
|
1902
2413
|
}
|
|
1903
2414
|
),
|
|
1904
|
-
/* @__PURE__ */ (0,
|
|
1905
|
-
/* @__PURE__ */ (0,
|
|
2415
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: cn2("rounded-full bg-slate-100 px-2 py-0.5 font-medium text-slate-600", BADGE_TEXT), children: getIssueOriginText(issue, copy) }),
|
|
2416
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-xs text-slate-400", children: formatRelativeTime(issue.updated_at) })
|
|
1906
2417
|
] })
|
|
1907
2418
|
] }),
|
|
1908
|
-
action && onAction ? /* @__PURE__ */ (0,
|
|
2419
|
+
action && onAction ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1909
2420
|
"button",
|
|
1910
2421
|
{
|
|
1911
2422
|
type: "button",
|
|
@@ -1915,8 +2426,8 @@ function IssueList({
|
|
|
1915
2426
|
}
|
|
1916
2427
|
) : null
|
|
1917
2428
|
] }),
|
|
1918
|
-
/* @__PURE__ */ (0,
|
|
1919
|
-
/* @__PURE__ */ (0,
|
|
2429
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-2 text-sm text-slate-600", children: truncate(issue.note) }),
|
|
2430
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-2 truncate text-xs text-slate-400", children: issue.target.page_url })
|
|
1920
2431
|
] })
|
|
1921
2432
|
] })
|
|
1922
2433
|
},
|
|
@@ -1925,7 +2436,7 @@ function IssueList({
|
|
|
1925
2436
|
}) });
|
|
1926
2437
|
}
|
|
1927
2438
|
function IssueReportPopover({ children }) {
|
|
1928
|
-
const [filter, setFilter] = (0,
|
|
2439
|
+
const [filter, setFilter] = (0, import_react7.useState)("all");
|
|
1929
2440
|
const {
|
|
1930
2441
|
copy,
|
|
1931
2442
|
isPopoverOpen,
|
|
@@ -1943,7 +2454,7 @@ function IssueReportPopover({ children }) {
|
|
|
1943
2454
|
const history = useIssueReportingHistory("all");
|
|
1944
2455
|
const status = useIssueReportingStatus();
|
|
1945
2456
|
const allItems = history.data?.items ?? [];
|
|
1946
|
-
const counts = (0,
|
|
2457
|
+
const counts = (0, import_react7.useMemo)(
|
|
1947
2458
|
() => ({
|
|
1948
2459
|
all: allItems.length,
|
|
1949
2460
|
unresolved: filterIssueReports(allItems, "unresolved").length,
|
|
@@ -1955,37 +2466,37 @@ function IssueReportPopover({ children }) {
|
|
|
1955
2466
|
const showSectionFirst = createMode === "surface_required" || createMode === "surface_preferred" && hasRegisteredTargets;
|
|
1956
2467
|
const sectionActionDisabled = !hasRegisteredTargets;
|
|
1957
2468
|
const helperText = createMode === "surface_required" ? hasRegisteredTargets ? copy.surfaceRequiredDescription : copy.specificSectionUnavailableDescription : createMode === "surface_preferred" && !hasRegisteredTargets ? copy.specificSectionUnavailableDescription : null;
|
|
1958
|
-
return /* @__PURE__ */ (0,
|
|
2469
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1959
2470
|
Popover.Root,
|
|
1960
2471
|
{
|
|
1961
2472
|
open: isPopoverOpen,
|
|
1962
2473
|
onOpenChange: (open) => open ? openPopover() : closePopover(),
|
|
1963
2474
|
children: [
|
|
1964
|
-
/* @__PURE__ */ (0,
|
|
1965
|
-
/* @__PURE__ */ (0,
|
|
2475
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover.Trigger, { asChild: true, children }),
|
|
2476
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1966
2477
|
Popover.Content,
|
|
1967
2478
|
{
|
|
1968
2479
|
align: "end",
|
|
1969
2480
|
side: "top",
|
|
1970
2481
|
sideOffset: 12,
|
|
1971
|
-
className:
|
|
2482
|
+
className: cn2(
|
|
1972
2483
|
"rounded-3xl border border-slate-200 bg-white p-4",
|
|
1973
2484
|
Z_POPOVER,
|
|
1974
2485
|
POPOVER_WIDTH,
|
|
1975
2486
|
POPOVER_SHADOW
|
|
1976
2487
|
),
|
|
1977
|
-
children: /* @__PURE__ */ (0,
|
|
1978
|
-
/* @__PURE__ */ (0,
|
|
1979
|
-
/* @__PURE__ */ (0,
|
|
1980
|
-
/* @__PURE__ */ (0,
|
|
1981
|
-
/* @__PURE__ */ (0,
|
|
2488
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-4", children: [
|
|
2489
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between gap-3", children: [
|
|
2490
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
2491
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "text-sm font-semibold text-slate-900", children: copy.popoverTitle }),
|
|
2492
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-1 text-xs text-slate-500", children: statusSummary })
|
|
1982
2493
|
] }),
|
|
1983
|
-
/* @__PURE__ */ (0,
|
|
1984
|
-
/* @__PURE__ */ (0,
|
|
2494
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex flex-wrap justify-end gap-2", children: showSectionFirst ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
2495
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1985
2496
|
"button",
|
|
1986
2497
|
{
|
|
1987
2498
|
type: "button",
|
|
1988
|
-
className:
|
|
2499
|
+
className: cn2(
|
|
1989
2500
|
"rounded-full px-3 py-2 text-xs font-semibold transition",
|
|
1990
2501
|
sectionActionDisabled ? "cursor-not-allowed bg-slate-200 text-slate-500" : "bg-slate-900 text-white hover:bg-slate-800"
|
|
1991
2502
|
),
|
|
@@ -2000,7 +2511,7 @@ function IssueReportPopover({ children }) {
|
|
|
2000
2511
|
children: createMode === "surface_required" ? copy.reportNewAction : copy.reportSpecificAction
|
|
2001
2512
|
}
|
|
2002
2513
|
),
|
|
2003
|
-
createMode !== "surface_required" ? /* @__PURE__ */ (0,
|
|
2514
|
+
createMode !== "surface_required" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2004
2515
|
"button",
|
|
2005
2516
|
{
|
|
2006
2517
|
type: "button",
|
|
@@ -2012,8 +2523,8 @@ function IssueReportPopover({ children }) {
|
|
|
2012
2523
|
children: copy.reportPageAction
|
|
2013
2524
|
}
|
|
2014
2525
|
) : null
|
|
2015
|
-
] }) : /* @__PURE__ */ (0,
|
|
2016
|
-
/* @__PURE__ */ (0,
|
|
2526
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
2527
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2017
2528
|
"button",
|
|
2018
2529
|
{
|
|
2019
2530
|
type: "button",
|
|
@@ -2025,7 +2536,7 @@ function IssueReportPopover({ children }) {
|
|
|
2025
2536
|
children: createMode === "general_page" ? copy.reportPageAction : copy.reportNewAction
|
|
2026
2537
|
}
|
|
2027
2538
|
),
|
|
2028
|
-
hasRegisteredTargets ? /* @__PURE__ */ (0,
|
|
2539
|
+
hasRegisteredTargets ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2029
2540
|
"button",
|
|
2030
2541
|
{
|
|
2031
2542
|
type: "button",
|
|
@@ -2039,10 +2550,10 @@ function IssueReportPopover({ children }) {
|
|
|
2039
2550
|
) : null
|
|
2040
2551
|
] }) })
|
|
2041
2552
|
] }),
|
|
2042
|
-
helperText ? /* @__PURE__ */ (0,
|
|
2043
|
-
status.error ? /* @__PURE__ */ (0,
|
|
2044
|
-
/* @__PURE__ */ (0,
|
|
2045
|
-
/* @__PURE__ */ (0,
|
|
2553
|
+
helperText ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rounded-2xl border border-amber-200 bg-amber-50 px-4 py-3 text-xs text-amber-900", children: helperText }) : null,
|
|
2554
|
+
status.error ? /* @__PURE__ */ (0, import_jsx_runtime3.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: [
|
|
2555
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: status.error.message || copy.statusLoadFailed }),
|
|
2556
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2046
2557
|
"button",
|
|
2047
2558
|
{
|
|
2048
2559
|
type: "button",
|
|
@@ -2052,16 +2563,16 @@ function IssueReportPopover({ children }) {
|
|
|
2052
2563
|
}
|
|
2053
2564
|
)
|
|
2054
2565
|
] }) : null,
|
|
2055
|
-
allowTenantScope ? /* @__PURE__ */ (0,
|
|
2056
|
-
/* @__PURE__ */ (0,
|
|
2057
|
-
/* @__PURE__ */ (0,
|
|
2566
|
+
allowTenantScope ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-2", children: [
|
|
2567
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: cn2("font-medium uppercase tracking-wide text-slate-500", LABEL_TEXT), children: copy.scopeLabel }),
|
|
2568
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex gap-2", children: [
|
|
2058
2569
|
["tenant", copy.scopeTenant],
|
|
2059
2570
|
["mine", copy.scopeMine]
|
|
2060
|
-
].map(([value, label]) => /* @__PURE__ */ (0,
|
|
2571
|
+
].map(([value, label]) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2061
2572
|
"button",
|
|
2062
2573
|
{
|
|
2063
2574
|
type: "button",
|
|
2064
|
-
className:
|
|
2575
|
+
className: cn2(
|
|
2065
2576
|
"rounded-full px-3 py-1.5 text-xs font-medium transition",
|
|
2066
2577
|
scope === value ? "bg-slate-900 text-white" : "bg-slate-100 text-slate-600 hover:bg-slate-200"
|
|
2067
2578
|
),
|
|
@@ -2071,15 +2582,15 @@ function IssueReportPopover({ children }) {
|
|
|
2071
2582
|
value
|
|
2072
2583
|
)) })
|
|
2073
2584
|
] }) : null,
|
|
2074
|
-
/* @__PURE__ */ (0,
|
|
2585
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex gap-2", children: [
|
|
2075
2586
|
["all", copy.filtersAll, counts.all],
|
|
2076
2587
|
["unresolved", copy.filtersUnresolved, counts.unresolved],
|
|
2077
2588
|
["resolved", copy.filtersResolved, counts.resolved]
|
|
2078
|
-
].map(([value, label, count]) => /* @__PURE__ */ (0,
|
|
2589
|
+
].map(([value, label, count]) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
2079
2590
|
"button",
|
|
2080
2591
|
{
|
|
2081
2592
|
type: "button",
|
|
2082
|
-
className:
|
|
2593
|
+
className: cn2(
|
|
2083
2594
|
"rounded-full px-3 py-1.5 text-xs font-medium transition",
|
|
2084
2595
|
filter === value ? "bg-slate-900 text-white" : "bg-slate-100 text-slate-600 hover:bg-slate-200"
|
|
2085
2596
|
),
|
|
@@ -2091,7 +2602,7 @@ function IssueReportPopover({ children }) {
|
|
|
2091
2602
|
},
|
|
2092
2603
|
value
|
|
2093
2604
|
)) }),
|
|
2094
|
-
/* @__PURE__ */ (0,
|
|
2605
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2095
2606
|
IssueList,
|
|
2096
2607
|
{
|
|
2097
2608
|
filter,
|
|
@@ -2099,7 +2610,7 @@ function IssueReportPopover({ children }) {
|
|
|
2099
2610
|
onReply: (issueReportId) => openExistingIssueModal(issueReportId, "reply")
|
|
2100
2611
|
}
|
|
2101
2612
|
),
|
|
2102
|
-
/* @__PURE__ */ (0,
|
|
2613
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "border-t border-slate-100 pt-3 text-xs leading-relaxed text-slate-500", children: copy.historyHelpText })
|
|
2103
2614
|
] })
|
|
2104
2615
|
}
|
|
2105
2616
|
) })
|
|
@@ -2117,42 +2628,36 @@ function IssueReportModal() {
|
|
|
2117
2628
|
retryModalHydration,
|
|
2118
2629
|
inputModes,
|
|
2119
2630
|
defaultInputMode,
|
|
2120
|
-
voice
|
|
2631
|
+
voice,
|
|
2632
|
+
screenshotCapture,
|
|
2633
|
+
createMode
|
|
2121
2634
|
} = useIssueReporting();
|
|
2122
2635
|
const { createMutation, updateMutation, replyMutation } = useIssueReportingMutations();
|
|
2123
|
-
const [note, setNote] = (0,
|
|
2124
|
-
const [submitError, setSubmitError] = (0,
|
|
2636
|
+
const [note, setNote] = (0, import_react7.useState)("");
|
|
2637
|
+
const [submitError, setSubmitError] = (0, import_react7.useState)(null);
|
|
2638
|
+
const [inputMode, setInputMode] = (0, import_react7.useState)(defaultInputMode);
|
|
2639
|
+
const [committedTranscript, setCommittedTranscript] = (0, import_react7.useState)("");
|
|
2640
|
+
const [voiceSubmitMetadata, setVoiceSubmitMetadata] = (0, import_react7.useState)(null);
|
|
2641
|
+
const [voiceStartRequestId, setVoiceStartRequestId] = (0, import_react7.useState)(0);
|
|
2642
|
+
const [voiceAppendRequestId, setVoiceAppendRequestId] = (0, import_react7.useState)(0);
|
|
2125
2643
|
const { isOpen, mode, issue, target, isHydrating, error } = modalState;
|
|
2126
2644
|
const canUseVoice = mode === "create" && inputModes.includes("voice");
|
|
2127
2645
|
const canUseText = mode !== "create" || inputModes.includes("text");
|
|
2128
2646
|
const canUseAttachments = Boolean(client.issueReporting.uploadAttachment);
|
|
2129
|
-
const existingAttachments = (0,
|
|
2647
|
+
const existingAttachments = (0, import_react7.useMemo)(
|
|
2130
2648
|
() => mode === "edit" && issue ? issue.attachments ?? [] : [],
|
|
2131
2649
|
[issue, mode]
|
|
2132
2650
|
);
|
|
2133
2651
|
const attachmentState = useAttachmentState(existingAttachments);
|
|
2134
2652
|
const isSubmitting = createMutation.isPending || updateMutation.isPending || replyMutation.isPending || attachmentState.uploadProgress.phase === "uploading";
|
|
2135
|
-
const {
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
isVoiceConnecting,
|
|
2144
|
-
isVoiceActive,
|
|
2145
|
-
resetVoiceCapture,
|
|
2146
|
-
startVoiceInput,
|
|
2147
|
-
stopVoiceInput,
|
|
2148
|
-
appendTranscript
|
|
2149
|
-
} = useIssueReportVoiceCapture({
|
|
2150
|
-
canUseVoice,
|
|
2151
|
-
defaultInputMode,
|
|
2152
|
-
isSubmitting,
|
|
2153
|
-
voice
|
|
2154
|
-
});
|
|
2155
|
-
(0, import_react5.useEffect)(() => {
|
|
2653
|
+
const resetVoiceCapture = (0, import_react7.useCallback)(() => {
|
|
2654
|
+
setInputMode(defaultInputMode);
|
|
2655
|
+
setCommittedTranscript("");
|
|
2656
|
+
setVoiceSubmitMetadata(null);
|
|
2657
|
+
setVoiceStartRequestId(0);
|
|
2658
|
+
setVoiceAppendRequestId(0);
|
|
2659
|
+
}, [defaultInputMode]);
|
|
2660
|
+
useIsomorphicLayoutEffect(() => {
|
|
2156
2661
|
if (!isOpen) {
|
|
2157
2662
|
resetVoiceCapture();
|
|
2158
2663
|
attachmentState.reset();
|
|
@@ -2178,14 +2683,17 @@ function IssueReportModal() {
|
|
|
2178
2683
|
attachmentState.reset();
|
|
2179
2684
|
closeModal();
|
|
2180
2685
|
};
|
|
2181
|
-
const
|
|
2182
|
-
|
|
2686
|
+
const handleRequestAppendTranscript = () => {
|
|
2687
|
+
setInputMode("voice");
|
|
2688
|
+
setVoiceAppendRequestId((current) => current + 1);
|
|
2183
2689
|
};
|
|
2184
|
-
const
|
|
2185
|
-
|
|
2690
|
+
const handleRequestStartVoiceInput = () => {
|
|
2691
|
+
setInputMode("voice");
|
|
2692
|
+
setVoiceStartRequestId((current) => current + 1);
|
|
2186
2693
|
};
|
|
2187
|
-
const handleAppendTranscript = () => {
|
|
2188
|
-
|
|
2694
|
+
const handleAppendTranscript = (transcript) => {
|
|
2695
|
+
setNote((current) => appendTranscriptToNote(current, transcript));
|
|
2696
|
+
setInputMode("text");
|
|
2189
2697
|
};
|
|
2190
2698
|
const uploadPendingFiles = async () => {
|
|
2191
2699
|
const upload = client.issueReporting.uploadAttachment;
|
|
@@ -2227,8 +2735,41 @@ function IssueReportModal() {
|
|
|
2227
2735
|
try {
|
|
2228
2736
|
const noteForSubmit = normalizedNote;
|
|
2229
2737
|
let newAttachmentIds = [];
|
|
2738
|
+
let screenshotCaptureMetadata = null;
|
|
2739
|
+
if (screenshotCapture?.enabled && mode === "create" && canUseAttachments && client.issueReporting.uploadAttachment) {
|
|
2740
|
+
try {
|
|
2741
|
+
const { captureScreenshot: captureScreenshot2 } = await Promise.resolve().then(() => (init_screenshot_capture(), screenshot_capture_exports));
|
|
2742
|
+
const captureResult = await captureScreenshot2(screenshotCapture, {
|
|
2743
|
+
pageUrl: target.page_url,
|
|
2744
|
+
createMode,
|
|
2745
|
+
inputMode: effectiveInputMode,
|
|
2746
|
+
target: {
|
|
2747
|
+
componentKey: target.component_key,
|
|
2748
|
+
componentLabel: target.component_label,
|
|
2749
|
+
surfaceRef: target.surface_ref,
|
|
2750
|
+
metadata: target.metadata
|
|
2751
|
+
}
|
|
2752
|
+
});
|
|
2753
|
+
const uploaded = await client.issueReporting.uploadAttachment(
|
|
2754
|
+
captureResult.blob,
|
|
2755
|
+
{ filename: captureResult.filename }
|
|
2756
|
+
);
|
|
2757
|
+
newAttachmentIds.push(uploaded.id);
|
|
2758
|
+
screenshotCaptureMetadata = buildScreenshotCaptureMetadata(
|
|
2759
|
+
screenshotCapture,
|
|
2760
|
+
"attached"
|
|
2761
|
+
);
|
|
2762
|
+
} catch (captureError) {
|
|
2763
|
+
screenshotCaptureMetadata = buildScreenshotCaptureMetadata(
|
|
2764
|
+
screenshotCapture,
|
|
2765
|
+
"failed"
|
|
2766
|
+
);
|
|
2767
|
+
screenshotCapture.onCaptureError?.(captureError);
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2230
2770
|
if (canUseAttachments && attachmentState.pendingFiles.length > 0) {
|
|
2231
|
-
|
|
2771
|
+
const manualIds = await uploadPendingFiles();
|
|
2772
|
+
newAttachmentIds.push(...manualIds);
|
|
2232
2773
|
}
|
|
2233
2774
|
const effectiveVoiceMetadata = effectiveInputMode === "voice" ? voiceSubmitMetadata ?? {
|
|
2234
2775
|
provider: voice.provider,
|
|
@@ -2239,20 +2780,26 @@ function IssueReportModal() {
|
|
|
2239
2780
|
attach_transcript: true
|
|
2240
2781
|
} : null;
|
|
2241
2782
|
if (mode === "create") {
|
|
2783
|
+
const targetMetadata = {
|
|
2784
|
+
...target.metadata ?? {},
|
|
2785
|
+
...screenshotCaptureMetadata ? {
|
|
2786
|
+
[SCREENSHOT_CAPTURE_METADATA_KEY]: screenshotCaptureMetadata
|
|
2787
|
+
} : {},
|
|
2788
|
+
...effectiveVoiceMetadata && effectiveInputMode === "voice" ? {
|
|
2789
|
+
capture: {
|
|
2790
|
+
input_mode: "voice",
|
|
2791
|
+
provider: effectiveVoiceMetadata.provider,
|
|
2792
|
+
model_id: effectiveVoiceMetadata.model_id,
|
|
2793
|
+
retain_audio: effectiveVoiceMetadata.retain_audio,
|
|
2794
|
+
attach_transcript: effectiveVoiceMetadata.attach_transcript
|
|
2795
|
+
}
|
|
2796
|
+
} : {}
|
|
2797
|
+
};
|
|
2242
2798
|
await createMutation.mutateAsync({
|
|
2243
|
-
target:
|
|
2799
|
+
target: {
|
|
2244
2800
|
...target,
|
|
2245
|
-
metadata:
|
|
2246
|
-
|
|
2247
|
-
capture: {
|
|
2248
|
-
input_mode: "voice",
|
|
2249
|
-
provider: effectiveVoiceMetadata.provider,
|
|
2250
|
-
model_id: effectiveVoiceMetadata.model_id,
|
|
2251
|
-
retain_audio: effectiveVoiceMetadata.retain_audio,
|
|
2252
|
-
attach_transcript: effectiveVoiceMetadata.attach_transcript
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
} : target,
|
|
2801
|
+
metadata: targetMetadata
|
|
2802
|
+
},
|
|
2256
2803
|
note: noteForSubmit,
|
|
2257
2804
|
reporter_role_hint: reporterRoleHint,
|
|
2258
2805
|
attachment_ids: newAttachmentIds.length > 0 ? newAttachmentIds : void 0
|
|
@@ -2277,7 +2824,7 @@ function IssueReportModal() {
|
|
|
2277
2824
|
attachmentState.reset();
|
|
2278
2825
|
closeModal();
|
|
2279
2826
|
} catch (submissionError) {
|
|
2280
|
-
const message =
|
|
2827
|
+
const message = resolveErrorMessage2(
|
|
2281
2828
|
submissionError,
|
|
2282
2829
|
"Failed to submit issue report"
|
|
2283
2830
|
);
|
|
@@ -2287,12 +2834,12 @@ function IssueReportModal() {
|
|
|
2287
2834
|
setSubmitError(message);
|
|
2288
2835
|
}
|
|
2289
2836
|
};
|
|
2290
|
-
return /* @__PURE__ */ (0,
|
|
2291
|
-
/* @__PURE__ */ (0,
|
|
2292
|
-
/* @__PURE__ */ (0,
|
|
2837
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Root, { open: isOpen, onOpenChange: (open) => !open && handleCloseModal(), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Dialog.Portal, { children: [
|
|
2838
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Overlay, { className: cn2("fixed inset-0 bg-slate-950/45 backdrop-blur-sm", Z_MODAL_OVERLAY) }),
|
|
2839
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
2293
2840
|
Dialog.Content,
|
|
2294
2841
|
{
|
|
2295
|
-
className:
|
|
2842
|
+
className: cn2(
|
|
2296
2843
|
"fixed left-1/2 top-1/2 max-h-[90vh] max-w-xl -translate-x-1/2 -translate-y-1/2 overflow-y-auto border border-slate-200 bg-white p-6 focus:outline-none",
|
|
2297
2844
|
Z_MODAL_CONTENT,
|
|
2298
2845
|
MODAL_WIDTH,
|
|
@@ -2300,8 +2847,8 @@ function IssueReportModal() {
|
|
|
2300
2847
|
MODAL_SHADOW
|
|
2301
2848
|
),
|
|
2302
2849
|
children: [
|
|
2303
|
-
/* @__PURE__ */ (0,
|
|
2304
|
-
/* @__PURE__ */ (0,
|
|
2850
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Title, { className: "text-lg font-semibold text-slate-950", children: title }),
|
|
2851
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Description, { className: "mt-2 text-sm text-slate-600", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2305
2852
|
IssueReportModalDescription,
|
|
2306
2853
|
{
|
|
2307
2854
|
copy,
|
|
@@ -2309,7 +2856,7 @@ function IssueReportModal() {
|
|
|
2309
2856
|
target
|
|
2310
2857
|
}
|
|
2311
2858
|
) }),
|
|
2312
|
-
/* @__PURE__ */ (0,
|
|
2859
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2313
2860
|
IssueReportModalBody,
|
|
2314
2861
|
{
|
|
2315
2862
|
copy,
|
|
@@ -2325,13 +2872,10 @@ function IssueReportModal() {
|
|
|
2325
2872
|
normalizedNote,
|
|
2326
2873
|
isValid,
|
|
2327
2874
|
isSubmitting,
|
|
2328
|
-
isVoiceActive,
|
|
2329
|
-
isVoiceConnecting,
|
|
2330
2875
|
voice,
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
scribeError,
|
|
2876
|
+
createVoiceToken: client.issueReporting.createVoiceToken,
|
|
2877
|
+
voiceStartRequestId,
|
|
2878
|
+
voiceAppendRequestId,
|
|
2335
2879
|
submitError,
|
|
2336
2880
|
existingAttachments,
|
|
2337
2881
|
removedExistingIds: attachmentState.removedExistingIds,
|
|
@@ -2342,8 +2886,10 @@ function IssueReportModal() {
|
|
|
2342
2886
|
onClose: handleCloseModal,
|
|
2343
2887
|
onSelectText: () => setInputMode("text"),
|
|
2344
2888
|
onSelectVoice: () => setInputMode("voice"),
|
|
2345
|
-
|
|
2346
|
-
|
|
2889
|
+
onRequestStartVoiceInput: handleRequestStartVoiceInput,
|
|
2890
|
+
onRequestAppendTranscript: handleRequestAppendTranscript,
|
|
2891
|
+
onTranscriptCommitted: setCommittedTranscript,
|
|
2892
|
+
onVoiceSubmitMetadataChange: setVoiceSubmitMetadata,
|
|
2347
2893
|
onAppendTranscript: handleAppendTranscript,
|
|
2348
2894
|
onNoteChange: setNote,
|
|
2349
2895
|
onSubmit: () => void handleSubmit(),
|
|
@@ -2352,13 +2898,13 @@ function IssueReportModal() {
|
|
|
2352
2898
|
onRemovePendingAttachment: attachmentState.removePending
|
|
2353
2899
|
}
|
|
2354
2900
|
),
|
|
2355
|
-
/* @__PURE__ */ (0,
|
|
2901
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2356
2902
|
"button",
|
|
2357
2903
|
{
|
|
2358
2904
|
type: "button",
|
|
2359
2905
|
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",
|
|
2360
2906
|
"aria-label": "Close issue report modal",
|
|
2361
|
-
children: /* @__PURE__ */ (0,
|
|
2907
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.X, { className: "h-4 w-4" })
|
|
2362
2908
|
}
|
|
2363
2909
|
) })
|
|
2364
2910
|
]
|
|
@@ -2385,28 +2931,28 @@ function FloatingIssueReportButton({
|
|
|
2385
2931
|
if (!isEligible) {
|
|
2386
2932
|
return null;
|
|
2387
2933
|
}
|
|
2388
|
-
return /* @__PURE__ */ (0,
|
|
2389
|
-
/* @__PURE__ */ (0,
|
|
2390
|
-
!isReportMode ? /* @__PURE__ */ (0,
|
|
2934
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
2935
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IssueReportModeBanner, {}),
|
|
2936
|
+
!isReportMode ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: cn2("fixed bottom-12 right-4", Z_FLOATING_BUTTON, positionClassName), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IssueReportPopover, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
2391
2937
|
"button",
|
|
2392
2938
|
{
|
|
2393
2939
|
type: "button",
|
|
2394
2940
|
"aria-label": needsResponse ? `${copy.entryAriaLabel} (${copy.threadNeedsResponseBadge})` : copy.entryAriaLabel,
|
|
2395
2941
|
onClick: () => isPopoverOpen ? closePopover() : openPopover(),
|
|
2396
|
-
className:
|
|
2942
|
+
className: cn2(
|
|
2397
2943
|
"relative flex h-12 w-12 items-center justify-center rounded-full border border-slate-200 bg-white shadow-lg transition hover:-translate-y-0.5 hover:shadow-xl focus:outline-none focus:ring-2 focus:ring-slate-300",
|
|
2398
2944
|
status.isPending && "animate-pulse",
|
|
2399
2945
|
className
|
|
2400
2946
|
),
|
|
2401
2947
|
children: [
|
|
2402
|
-
/* @__PURE__ */ (0,
|
|
2403
|
-
|
|
2948
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2949
|
+
import_react6.BugBeetle,
|
|
2404
2950
|
{
|
|
2405
|
-
className:
|
|
2951
|
+
className: cn2("h-6 w-6", getEntryPointClassName(entryPointState)),
|
|
2406
2952
|
weight: "fill"
|
|
2407
2953
|
}
|
|
2408
2954
|
),
|
|
2409
|
-
needsResponse ? /* @__PURE__ */ (0,
|
|
2955
|
+
needsResponse ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2410
2956
|
"span",
|
|
2411
2957
|
{
|
|
2412
2958
|
"data-testid": "issue-report-needs-response-badge",
|
|
@@ -2416,7 +2962,7 @@ function FloatingIssueReportButton({
|
|
|
2416
2962
|
]
|
|
2417
2963
|
}
|
|
2418
2964
|
) }) }) : null,
|
|
2419
|
-
/* @__PURE__ */ (0,
|
|
2965
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IssueReportModal, {})
|
|
2420
2966
|
] });
|
|
2421
2967
|
}
|
|
2422
2968
|
function ReportableSection({
|
|
@@ -2427,9 +2973,9 @@ function ReportableSection({
|
|
|
2427
2973
|
}) {
|
|
2428
2974
|
const reportMode = useReportMode();
|
|
2429
2975
|
const isSelectable = Boolean(reportMode?.isReportMode);
|
|
2430
|
-
const targetId =
|
|
2431
|
-
const elementRef =
|
|
2432
|
-
(0,
|
|
2976
|
+
const targetId = import_react7.default.useRef(/* @__PURE__ */ Symbol("reportable-section"));
|
|
2977
|
+
const elementRef = import_react7.default.useRef(null);
|
|
2978
|
+
(0, import_react7.useEffect)(() => {
|
|
2433
2979
|
if (!reportMode) {
|
|
2434
2980
|
return;
|
|
2435
2981
|
}
|
|
@@ -2438,13 +2984,13 @@ function ReportableSection({
|
|
|
2438
2984
|
reportMode.unregisterTarget(targetId.current);
|
|
2439
2985
|
};
|
|
2440
2986
|
}, [reportMode, reportableName]);
|
|
2441
|
-
return
|
|
2987
|
+
return import_react7.default.createElement(
|
|
2442
2988
|
Component,
|
|
2443
2989
|
{
|
|
2444
2990
|
ref: (node) => {
|
|
2445
2991
|
elementRef.current = node;
|
|
2446
2992
|
},
|
|
2447
|
-
className:
|
|
2993
|
+
className: cn2(
|
|
2448
2994
|
className,
|
|
2449
2995
|
isSelectable && "cursor-pointer ring-2 ring-amber-400 transition hover:ring-amber-500"
|
|
2450
2996
|
),
|