spaps-issue-reporting-react 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
- No changes yet.
|
|
6
|
+
|
|
7
|
+
## [0.6.0] - 2026-06-07
|
|
8
|
+
|
|
9
|
+
- Lazy-load voice input (`@elevenlabs/react`) behind a dynamic `import()` so consumers no longer eager-load `livekit-client` (~492KB raw / ~140KB gzip) on first paint. The Scribe voice-capture UI/logic now lives in a code-split `VoiceCapture` chunk that is only fetched when the reporter switches to voice input (`inputMode === "voice"` and voice is permitted); text-only reporting never touches `@elevenlabs`/livekit. Public API (`IssueReportingProvider`, `FloatingIssueReportButton`, `IssueReportingPageConfig`, `ReportableSection`, `useIssueReporting`) and behavior are unchanged.
|
|
10
|
+
- Added opt-in automatic same-origin DOM screenshot capture through `IssueReportingProvider.screenshotCapture`. Capture lazy-loads `html2canvas` only during create submit when enabled, uploads through the host client's `issueReporting.uploadAttachment(file, { filename })`, records coarse `target.metadata.screenshot_capture` status, and continues issue submission without a screenshot if capture/render/upload fails.
|
|
11
|
+
- Added the public `spaps-issue-reporting-react/screenshot-capture` subpath so app-local adapters such as custom ref-area tools can reuse the shared capture, masking, compression, and filename logic without copying generic `html2canvas` code.
|
|
12
|
+
- Added optional `html2canvas >=1.4.0` peer metadata and documented the privacy controls: `data-spaps-screenshot-exclude`, `data-spaps-private`, host `excludeSelectors`, host `maskSelectors`, visible-viewport capture, private SPAPS attachment storage, 10 MiB file limit, and five-attachment cap.
|
|
13
|
+
- Release handoff: `spaps-issue-reporting-react@0.6.0` is still operator-gated because npm currently serves `0.5.0`. Local consumer proofs used `/tmp/spaps-issue-reporting-react-0.6.0-normalized.tgz`, with `spaps-types@1.5.0` and `spaps-sdk@1.11.0` already available on npm. After operator approval, publish `spaps-issue-reporting-react@0.6.0`, reinstall downstream consumers from the registry, and re-run their focused screenshot checks before deployment.
|
|
14
|
+
|
|
15
|
+
## [0.4.2] - 2026-06-04
|
|
16
|
+
|
|
5
17
|
- Added the reporter-visible operator-to-reporter conversation thread (`IssueReportMessageThread`), rendered inside the issue detail modal when the client implements `issueReporting.listMessages`/`submitMessage`. It shows only `active`, reporter-visible projection rows (retracted/superseded/non-visible excluded), distinguishes operator vs reporter authorship and message kind, exposes a `needs_response` affordance plus a floating-entrypoint badge, and includes a clarification-response composer that submits with a client-generated `idempotency_key` and handles `409 ISSUE_REPORT_MESSAGE_CONFLICT`.
|
|
6
18
|
|
|
7
19
|
## [0.4.1] - 2026-05-16
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ This package targets `Node.js >=18` and React 18+. Voice input uses the package'
|
|
|
18
18
|
| --- | --- |
|
|
19
19
|
| A visible issue-report entry point | Floating button with open/recent state |
|
|
20
20
|
| A guided reporting flow | Page-first create flow, optional section picking, create/edit/reply modal |
|
|
21
|
-
| Screenshot evidence | Optional picker that
|
|
21
|
+
| Screenshot evidence | Optional picker and opt-in lazy DOM capture that upload private PNG/JPEG/WebP attachments through the host client |
|
|
22
22
|
| A lightweight integration contract | Any client that exposes `issueReporting.*` methods |
|
|
23
23
|
| App-level control | Eligibility, reporter identity, scope, page policy, and copy stay in your app |
|
|
24
24
|
|
|
@@ -73,6 +73,7 @@ export function AppShell() {
|
|
|
73
73
|
- A client with `issueReporting.getStatus`, `list`, `get`, `create`, `update`, and `reply`.
|
|
74
74
|
- A client with `issueReporting.createVoiceToken` when `inputModes` includes `voice`.
|
|
75
75
|
- A client with `issueReporting.uploadAttachment(file, { filename })` when you want screenshot uploads.
|
|
76
|
+
- For automatic screenshot capture, `spaps-sdk >=1.10.1` or an equivalent client that implements `issueReporting.uploadAttachment(file, { filename })`.
|
|
76
77
|
- Optionally, a client with `issueReporting.listMessages(issueReportId)` and
|
|
77
78
|
`issueReporting.submitMessage(issueReportId, { body, idempotency_key })` to wire the
|
|
78
79
|
operator-to-reporter message thread (and its `needs_response` badge). When these are present,
|
|
@@ -151,7 +152,93 @@ const client = {
|
|
|
151
152
|
|
|
152
153
|
The picker accepts PNG, JPEG, and WebP files, enforces the 10 MiB per-file limit and 5-screenshot report limit, previews pending files, and preserves existing attachments while editing. On submit, it uploads pending files first and sends only attachment IDs through `attachment_ids`, `add_attachment_ids`, or `remove_attachment_ids`.
|
|
153
154
|
|
|
154
|
-
This package does not store screenshots, generate public URLs, or
|
|
155
|
+
This package does not store screenshots, generate public URLs, or perform pixel/OCR redaction after capture. SPAPS stores screenshots through its shared hosted-asset boundary as private objects, and access URLs are minted by the backend after authorization. The automatic capture path masks common sensitive controls and token-like text in the cloned DOM before rendering, but host apps should still warn users before upload when a screenshot might include PHI, credentials, payment data, or other sensitive content.
|
|
156
|
+
|
|
157
|
+
### Automatic Screenshot Capture
|
|
158
|
+
|
|
159
|
+
Automatic DOM capture is opt-in and lazy. Apps that omit `screenshotCapture` or set `enabled: false` get no behavioral change and do not download `html2canvas`.
|
|
160
|
+
|
|
161
|
+
**Peer dependency:** install `html2canvas` (>=1.4.0) as an optional peer in the consuming app. The capture module only loads via dynamic `import()` when a report is submitted with capture enabled.
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
npm install html2canvas
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
<IssueReportingProvider
|
|
169
|
+
client={spaps}
|
|
170
|
+
isEligible={true}
|
|
171
|
+
screenshotCapture={{
|
|
172
|
+
enabled: true,
|
|
173
|
+
captureOn: "before_submit",
|
|
174
|
+
scope: "visible_viewport",
|
|
175
|
+
imageType: "image/png",
|
|
176
|
+
excludeSelectors: [
|
|
177
|
+
"[data-spaps-screenshot-exclude]",
|
|
178
|
+
"[data-spaps-private]",
|
|
179
|
+
],
|
|
180
|
+
failureBehavior: "continue_without_screenshot",
|
|
181
|
+
onCaptureError: (err) => console.warn("Screenshot capture failed:", err),
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
184
|
+
<FloatingIssueReportButton />
|
|
185
|
+
</IssueReportingProvider>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`screenshotCapture` maps to `IssueReportingScreenshotCaptureConfig` in `src/types.ts`:
|
|
189
|
+
|
|
190
|
+
| Field | Behavior |
|
|
191
|
+
| --- | --- |
|
|
192
|
+
| `enabled` | Required opt-in. `false` or an absent prop means no DOM capture, no lazy import, and no behavior change. |
|
|
193
|
+
| `captureOn` | Only `"before_submit"` is supported: capture after the reporter submits and before pending attachments upload. |
|
|
194
|
+
| `scope` | `"visible_viewport"` by default. `"selected_reportable_target"` records the selected target in capture context; to narrow pixels to that section, pass `rootElement` or a getter that returns the selected same-origin element. |
|
|
195
|
+
| `imageType` | Defaults to `"image/png"`; must stay within SPAPS-supported PNG/JPEG/WebP uploads. |
|
|
196
|
+
| `filename` | Optional static name or callback `(context) => string`. The upload flows through `issueReporting.uploadAttachment(file, { filename })`. |
|
|
197
|
+
| `rootElement` | Optional same-origin element or getter for host shells that need to exclude app chrome or capture a selected section. If omitted, capture starts from `document.documentElement` and is cropped to the visible viewport. |
|
|
198
|
+
| `rect` | Optional visible-viewport crop rectangle or getter. Custom consumers can use this for click-and-drag/refarea tools while still sharing the package capture implementation. |
|
|
199
|
+
| `excludeSelectors` | Optional extra selectors to omit. The implementation always honors `[data-spaps-screenshot-exclude]` and `[data-spaps-private]` as privacy markers. Invalid custom selectors are ignored so they cannot block submission. |
|
|
200
|
+
| `maskSelectors` | Optional selectors to mask in the cloned DOM before rendering. Defaults mask form controls and editable regions. |
|
|
201
|
+
| `pixelRatio` | Optional render scale, clamped to `1..2`. Defaults to `window.devicePixelRatio` within that range. |
|
|
202
|
+
| `quality` | Optional image quality passed to canvas encoding for lossy formats. |
|
|
203
|
+
| `maxBytes` | Cannot exceed the SPAPS 10 MiB attachment limit. An automatic screenshot counts toward the existing five attachment cap. |
|
|
204
|
+
| `failureBehavior` | Only `"continue_without_screenshot"` is supported. Capture/render/upload failure does not block issue submission. |
|
|
205
|
+
| `onCaptureError` | Optional host callback for telemetry or a local warning when capture fails. Receives the raw browser error; raw error text is not stored in issue metadata by this package. |
|
|
206
|
+
|
|
207
|
+
Minimum SDK capability: `spaps-sdk >=1.10.1` or an equivalent client implementing `issueReporting.uploadAttachment(file, options)` returning `IssueReportAttachmentOut`.
|
|
208
|
+
|
|
209
|
+
What is captured:
|
|
210
|
+
|
|
211
|
+
- Automatic capture is opt-in and lazy. It never runs on provider mount, page load, history view, edit, reply, or when the upload helper is absent.
|
|
212
|
+
- The capture module dynamically imports `html2canvas` only during create submit after `screenshotCapture.enabled === true`.
|
|
213
|
+
- Capture is cropped to the current visible viewport dimensions. It does not capture hidden browser chrome, full-page scrollback, background tabs, or data outside the visible page.
|
|
214
|
+
- `html2canvas` renders same-origin DOM it can access. Cross-origin iframes and tainted canvases are not a supported capture target.
|
|
215
|
+
|
|
216
|
+
Custom consumers that do not use the shared modal can import the same primitive without reaching into private build output:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { captureScreenshot } from "spaps-issue-reporting-react/screenshot-capture";
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The subpath also exports `maskScreenshotCaptureDocument`, `SCREENSHOT_CAPTURE_REDACTION_TEXT`, and the related screenshot capture types for app-local adapters.
|
|
223
|
+
|
|
224
|
+
Sensitive-region exclusion:
|
|
225
|
+
|
|
226
|
+
- Mark sensitive DOM with `data-spaps-screenshot-exclude` or `data-spaps-private`.
|
|
227
|
+
- Add host-specific `excludeSelectors` for reusable sensitive regions such as account numbers, tokens, patient identifiers, or payment fields.
|
|
228
|
+
- Exclusion is omission during rendering, not image-content redaction after capture. The capture path masks common cloned DOM controls and token-like text, but it does not inspect, blur, OCR, or redact rendered pixels. Host apps own warnings and exclusion markers for sensitive surfaces.
|
|
229
|
+
|
|
230
|
+
Capture metadata and fallback:
|
|
231
|
+
|
|
232
|
+
- On successful automatic capture, create payload metadata includes `target.metadata.screenshot_capture = { status: "attached", scope, image_type }`.
|
|
233
|
+
- On capture/render/upload failure, create payload metadata includes `target.metadata.screenshot_capture = { status: "failed", scope, image_type, failure_reason: "capture_failed" }`.
|
|
234
|
+
- Failure metadata intentionally stores a coarse reason only. Use `onCaptureError` for app-local telemetry if you need the raw error.
|
|
235
|
+
- Capture failures continue issue submission with text/voice metadata and any manual attachments.
|
|
236
|
+
- Preserves the SPAPS hosted-asset constraints: private objects, PNG/JPEG/WebP only, 10 MiB per file, and five attachments per report.
|
|
237
|
+
- If the blob exceeds `maxBytes`, the module compresses automatically (falling back to JPEG at decreasing quality). If compression still exceeds the limit, the error fires through `onCaptureError` and submission continues without the screenshot.
|
|
238
|
+
|
|
239
|
+
Operator-gated release:
|
|
240
|
+
|
|
241
|
+
Publishing this version requires operator approval. Consumers may opt in by passing `screenshotCapture={{ enabled: true }}` only after the approved package version is published to npm.
|
|
155
242
|
|
|
156
243
|
## Conversation Thread
|
|
157
244
|
|
|
@@ -313,7 +400,7 @@ and revisit trigger.
|
|
|
313
400
|
## Metadata
|
|
314
401
|
|
|
315
402
|
- `package_name`: `spaps-issue-reporting-react`
|
|
316
|
-
- `latest_version`: `0.
|
|
403
|
+
- `latest_version`: `0.6.0`
|
|
317
404
|
- `minimum_runtime`: `Node.js >=18.0.0`
|
|
318
405
|
- `api_base_url`: `https://api.sweetpotato.dev`
|
|
319
406
|
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// src/voice/VoiceCapture.tsx
|
|
2
|
+
import { useScribe } from "@elevenlabs/react";
|
|
3
|
+
import { Microphone, Spinner, TextT } from "@phosphor-icons/react";
|
|
4
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
function cn(...values) {
|
|
7
|
+
return values.filter(Boolean).join(" ");
|
|
8
|
+
}
|
|
9
|
+
function resolveErrorMessage(error, fallback) {
|
|
10
|
+
if (error instanceof Error && error.message.trim()) {
|
|
11
|
+
return error.message;
|
|
12
|
+
}
|
|
13
|
+
if (error && typeof error === "object" && "message" in error && typeof error.message === "string" && error.message.trim()) {
|
|
14
|
+
return error.message;
|
|
15
|
+
}
|
|
16
|
+
return fallback;
|
|
17
|
+
}
|
|
18
|
+
function getCommittedTranscriptText(committedTranscripts) {
|
|
19
|
+
return committedTranscripts.map((segment) => segment.text.trim()).filter(Boolean).join(" ").trim();
|
|
20
|
+
}
|
|
21
|
+
function IssueReportVoicePanel({
|
|
22
|
+
canUseText,
|
|
23
|
+
effectiveInputMode,
|
|
24
|
+
isSubmitting,
|
|
25
|
+
isVoiceActive,
|
|
26
|
+
isVoiceConnecting,
|
|
27
|
+
voice,
|
|
28
|
+
voiceTokenResult,
|
|
29
|
+
committedTranscript,
|
|
30
|
+
voiceError,
|
|
31
|
+
scribeError,
|
|
32
|
+
onSelectText,
|
|
33
|
+
onSelectVoice,
|
|
34
|
+
onStartVoiceInput,
|
|
35
|
+
onStopVoiceInput,
|
|
36
|
+
onAppendTranscript
|
|
37
|
+
}) {
|
|
38
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
39
|
+
canUseText ? /* @__PURE__ */ jsxs("div", { className: "mt-5 flex gap-2", children: [
|
|
40
|
+
/* @__PURE__ */ jsxs(
|
|
41
|
+
"button",
|
|
42
|
+
{
|
|
43
|
+
type: "button",
|
|
44
|
+
className: cn(
|
|
45
|
+
"inline-flex items-center gap-1 rounded-full border px-3 py-1.5 text-xs font-semibold transition",
|
|
46
|
+
effectiveInputMode === "text" ? "border-slate-900 bg-slate-900 text-white" : "border-slate-200 text-slate-700 hover:bg-slate-50"
|
|
47
|
+
),
|
|
48
|
+
onClick: onSelectText,
|
|
49
|
+
disabled: isSubmitting,
|
|
50
|
+
children: [
|
|
51
|
+
/* @__PURE__ */ jsx(TextT, { className: "h-3.5 w-3.5" }),
|
|
52
|
+
"Text Input"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
),
|
|
56
|
+
/* @__PURE__ */ jsxs(
|
|
57
|
+
"button",
|
|
58
|
+
{
|
|
59
|
+
type: "button",
|
|
60
|
+
className: cn(
|
|
61
|
+
"inline-flex items-center gap-1 rounded-full border px-3 py-1.5 text-xs font-semibold transition",
|
|
62
|
+
effectiveInputMode === "voice" ? "border-slate-900 bg-slate-900 text-white" : "border-slate-200 text-slate-700 hover:bg-slate-50"
|
|
63
|
+
),
|
|
64
|
+
onClick: onSelectVoice,
|
|
65
|
+
disabled: isSubmitting,
|
|
66
|
+
children: [
|
|
67
|
+
/* @__PURE__ */ jsx(Microphone, { className: "h-3.5 w-3.5" }),
|
|
68
|
+
"Voice Input"
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
] }) : null,
|
|
73
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: [
|
|
74
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
75
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
76
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: "Voice Input" }),
|
|
77
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-slate-600", children: [
|
|
78
|
+
"Provider: ",
|
|
79
|
+
voiceTokenResult?.provider ?? voice.provider,
|
|
80
|
+
" \xB7 Model:",
|
|
81
|
+
" ",
|
|
82
|
+
voiceTokenResult?.model_id ?? voice.modelId
|
|
83
|
+
] }),
|
|
84
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-slate-500", children: voice.requireMicrophonePermission ? "Microphone access is required to transcribe." : "Microphone access policy is optional." })
|
|
85
|
+
] }),
|
|
86
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
87
|
+
isVoiceActive || isVoiceConnecting ? /* @__PURE__ */ jsx(
|
|
88
|
+
"button",
|
|
89
|
+
{
|
|
90
|
+
type: "button",
|
|
91
|
+
className: "rounded-full border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-100",
|
|
92
|
+
onClick: onStopVoiceInput,
|
|
93
|
+
disabled: isSubmitting,
|
|
94
|
+
children: "Stop Voice Input"
|
|
95
|
+
}
|
|
96
|
+
) : /* @__PURE__ */ jsx(
|
|
97
|
+
"button",
|
|
98
|
+
{
|
|
99
|
+
type: "button",
|
|
100
|
+
className: "rounded-full bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-slate-800",
|
|
101
|
+
onClick: onStartVoiceInput,
|
|
102
|
+
disabled: isSubmitting,
|
|
103
|
+
children: "Start Voice Input"
|
|
104
|
+
}
|
|
105
|
+
),
|
|
106
|
+
canUseText ? /* @__PURE__ */ jsx(
|
|
107
|
+
"button",
|
|
108
|
+
{
|
|
109
|
+
type: "button",
|
|
110
|
+
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",
|
|
111
|
+
onClick: onAppendTranscript,
|
|
112
|
+
disabled: isSubmitting || !committedTranscript.trim(),
|
|
113
|
+
children: "Append Transcript"
|
|
114
|
+
}
|
|
115
|
+
) : null
|
|
116
|
+
] })
|
|
117
|
+
] }),
|
|
118
|
+
/* @__PURE__ */ 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__ */ jsx("span", { className: "text-slate-500", children: "No committed transcript yet." }) }),
|
|
119
|
+
voiceError || scribeError ? /* @__PURE__ */ 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__ */ jsxs("div", { className: "mt-2 flex items-center gap-1.5 text-xs text-slate-500", children: [
|
|
120
|
+
/* @__PURE__ */ jsx(Spinner, { className: "h-3.5 w-3.5 animate-spin" }),
|
|
121
|
+
"Connecting voice transcription..."
|
|
122
|
+
] }) : null
|
|
123
|
+
] })
|
|
124
|
+
] });
|
|
125
|
+
}
|
|
126
|
+
function VoiceCapture({
|
|
127
|
+
canUseText,
|
|
128
|
+
effectiveInputMode,
|
|
129
|
+
isSubmitting,
|
|
130
|
+
voice,
|
|
131
|
+
createVoiceToken,
|
|
132
|
+
startRequestId,
|
|
133
|
+
appendRequestId,
|
|
134
|
+
onSelectText,
|
|
135
|
+
onSelectVoice,
|
|
136
|
+
onTranscriptCommitted,
|
|
137
|
+
onSubmitMetadataChange,
|
|
138
|
+
onAppendTranscript
|
|
139
|
+
}) {
|
|
140
|
+
const [voiceTokenResult, setVoiceTokenResult] = useState(null);
|
|
141
|
+
const [voiceSubmitMetadata, setVoiceSubmitMetadata] = useState(null);
|
|
142
|
+
const [voiceError, setVoiceError] = useState(null);
|
|
143
|
+
const startInFlightRef = useRef(false);
|
|
144
|
+
const lastStartRequestId = useRef(0);
|
|
145
|
+
const lastAppendRequestId = useRef(0);
|
|
146
|
+
const {
|
|
147
|
+
status: scribeStatus,
|
|
148
|
+
isConnected,
|
|
149
|
+
isTranscribing,
|
|
150
|
+
committedTranscripts,
|
|
151
|
+
error: scribeError,
|
|
152
|
+
connect: connectScribe,
|
|
153
|
+
disconnect: disconnectScribe
|
|
154
|
+
} = useScribe({
|
|
155
|
+
autoConnect: false,
|
|
156
|
+
modelId: voice.modelId,
|
|
157
|
+
microphone: voice.microphone
|
|
158
|
+
});
|
|
159
|
+
const committedTranscript = useMemo(
|
|
160
|
+
() => getCommittedTranscriptText(committedTranscripts),
|
|
161
|
+
[committedTranscripts]
|
|
162
|
+
);
|
|
163
|
+
const isVoiceConnecting = scribeStatus === "connecting";
|
|
164
|
+
const isVoiceActive = isConnected || isTranscribing;
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
onTranscriptCommitted(committedTranscript);
|
|
167
|
+
}, [committedTranscript, onTranscriptCommitted]);
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
onSubmitMetadataChange(voiceSubmitMetadata);
|
|
170
|
+
}, [onSubmitMetadataChange, voiceSubmitMetadata]);
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
return () => {
|
|
173
|
+
disconnectScribe();
|
|
174
|
+
};
|
|
175
|
+
}, [disconnectScribe]);
|
|
176
|
+
const startVoiceInput = useCallback(async () => {
|
|
177
|
+
if (startInFlightRef.current || isSubmitting || isVoiceActive || isVoiceConnecting) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (!createVoiceToken) {
|
|
181
|
+
setVoiceError("Voice input is unavailable for this client.");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
startInFlightRef.current = true;
|
|
185
|
+
setVoiceError(null);
|
|
186
|
+
try {
|
|
187
|
+
const tokenResult = await createVoiceToken();
|
|
188
|
+
setVoiceTokenResult(tokenResult);
|
|
189
|
+
setVoiceSubmitMetadata(tokenResult);
|
|
190
|
+
await connectScribe({
|
|
191
|
+
token: tokenResult.token,
|
|
192
|
+
modelId: tokenResult.model_id,
|
|
193
|
+
microphone: voice.microphone
|
|
194
|
+
});
|
|
195
|
+
onSelectVoice();
|
|
196
|
+
} catch (startError) {
|
|
197
|
+
setVoiceError(resolveErrorMessage(startError, "Failed to start voice input."));
|
|
198
|
+
} finally {
|
|
199
|
+
startInFlightRef.current = false;
|
|
200
|
+
}
|
|
201
|
+
}, [
|
|
202
|
+
connectScribe,
|
|
203
|
+
createVoiceToken,
|
|
204
|
+
isSubmitting,
|
|
205
|
+
isVoiceActive,
|
|
206
|
+
isVoiceConnecting,
|
|
207
|
+
onSelectVoice,
|
|
208
|
+
voice.microphone
|
|
209
|
+
]);
|
|
210
|
+
const appendTranscript = useCallback(() => {
|
|
211
|
+
if (!committedTranscript.trim()) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
onAppendTranscript(committedTranscript);
|
|
215
|
+
}, [committedTranscript, onAppendTranscript]);
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
if (startRequestId === 0 || startRequestId === lastStartRequestId.current) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
lastStartRequestId.current = startRequestId;
|
|
221
|
+
void startVoiceInput();
|
|
222
|
+
}, [startRequestId, startVoiceInput]);
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
if (appendRequestId === 0 || appendRequestId === lastAppendRequestId.current) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
lastAppendRequestId.current = appendRequestId;
|
|
228
|
+
appendTranscript();
|
|
229
|
+
}, [appendRequestId, appendTranscript]);
|
|
230
|
+
return /* @__PURE__ */ jsx(
|
|
231
|
+
IssueReportVoicePanel,
|
|
232
|
+
{
|
|
233
|
+
canUseText,
|
|
234
|
+
effectiveInputMode,
|
|
235
|
+
isSubmitting,
|
|
236
|
+
isVoiceActive,
|
|
237
|
+
isVoiceConnecting,
|
|
238
|
+
voice,
|
|
239
|
+
voiceTokenResult,
|
|
240
|
+
committedTranscript,
|
|
241
|
+
voiceError,
|
|
242
|
+
scribeError,
|
|
243
|
+
onSelectText,
|
|
244
|
+
onSelectVoice,
|
|
245
|
+
onStartVoiceInput: () => void startVoiceInput(),
|
|
246
|
+
onStopVoiceInput: disconnectScribe,
|
|
247
|
+
onAppendTranscript: appendTranscript
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
var VoiceCapture_default = VoiceCapture;
|
|
252
|
+
export {
|
|
253
|
+
VoiceCapture,
|
|
254
|
+
VoiceCapture_default as default
|
|
255
|
+
};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,143 +1,13 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import React__default
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
export { CreateIssueReportRequest, CreateReporterMessageRequest, IssueReport, IssueReportAttachmentOut, IssueReportListResult, IssueReportMessage, IssueReportScope, IssueReportStatus, IssueReportStatusResult, ListIssueReportMessagesResponse, ReplyIssueReportRequest, UpdateIssueReportRequest } from 'spaps-types';
|
|
3
|
+
import React__default from 'react';
|
|
4
|
+
import { F as FloatingIssueReportButtonProps, R as ReportableInput, I as IssueReportingPageConfigProps, a as IssueReportingProviderProps, b as IssueReportingCopy, c as IssueHistoryFilter, d as IssueReportingCreateMode, e as IssueReportingVoiceConfig, f as IssueReportingScreenshotCaptureConfig } from './types-D9U13O2X.mjs';
|
|
5
|
+
export { g as IssueReportingClient, h as IssueReportingScreenshotCaptureContext, i as IssueReportingScreenshotCaptureFailureBehavior, j as IssueReportingScreenshotCaptureRect, k as IssueReportingScreenshotCaptureScope, l as IssueReportingScreenshotCaptureTiming, m as IssueReportingScreenshotImageType, n as ReportableTargetDescriptor } from './types-D9U13O2X.mjs';
|
|
7
6
|
import * as _tanstack_query_core from '@tanstack/query-core';
|
|
8
7
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
echoCancellation?: boolean;
|
|
13
|
-
noiseSuppression?: boolean;
|
|
14
|
-
autoGainControl?: boolean;
|
|
15
|
-
channelCount?: number;
|
|
16
|
-
}
|
|
17
|
-
interface IssueReportingVoiceConfig {
|
|
18
|
-
provider?: IssueReportingVoiceProvider;
|
|
19
|
-
modelId?: string;
|
|
20
|
-
requireMicrophonePermission?: boolean;
|
|
21
|
-
microphone?: IssueReportingVoiceMicrophoneConfig;
|
|
22
|
-
}
|
|
23
|
-
interface IssueReportingClient {
|
|
24
|
-
issueReporting: {
|
|
25
|
-
getStatus: (params?: {
|
|
26
|
-
scope?: IssueReportScope;
|
|
27
|
-
}) => Promise<IssueReportStatusResult>;
|
|
28
|
-
list: (params?: {
|
|
29
|
-
status?: IssueReportStatus;
|
|
30
|
-
limit?: number;
|
|
31
|
-
offset?: number;
|
|
32
|
-
scope?: IssueReportScope;
|
|
33
|
-
}) => Promise<IssueReportListResult>;
|
|
34
|
-
get: (issueReportId: string) => Promise<IssueReport>;
|
|
35
|
-
create: (payload: CreateIssueReportRequest) => Promise<IssueReport>;
|
|
36
|
-
update: (issueReportId: string, payload: UpdateIssueReportRequest) => Promise<IssueReport>;
|
|
37
|
-
reply: (issueReportId: string, payload: ReplyIssueReportRequest) => Promise<IssueReport>;
|
|
38
|
-
createVoiceToken?: () => Promise<IssueReportingVoiceTokenResult>;
|
|
39
|
-
uploadAttachment?: (file: Blob, options?: {
|
|
40
|
-
filename?: string;
|
|
41
|
-
}) => Promise<IssueReportAttachmentOut>;
|
|
42
|
-
listMessages?: (issueReportId: string) => Promise<ListIssueReportMessagesResponse>;
|
|
43
|
-
submitMessage?: (issueReportId: string, payload: CreateReporterMessageRequest) => Promise<IssueReportMessage>;
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
interface ReportableTargetDescriptor {
|
|
47
|
-
componentKey: string;
|
|
48
|
-
componentLabel?: string;
|
|
49
|
-
surfaceRef?: string | null;
|
|
50
|
-
metadata?: Record<string, unknown>;
|
|
51
|
-
}
|
|
52
|
-
type ReportableInput = string | ReportableTargetDescriptor;
|
|
53
|
-
type IssueReportingCreateMode = "general_page" | "surface_required" | "surface_preferred";
|
|
54
|
-
type IssueHistoryFilter = "all" | "unresolved" | "resolved";
|
|
55
|
-
interface IssueReportingCopy {
|
|
56
|
-
entryAriaLabel: string;
|
|
57
|
-
popoverTitle: string;
|
|
58
|
-
reportNewAction: string;
|
|
59
|
-
reportPageAction: string;
|
|
60
|
-
reportSpecificAction: string;
|
|
61
|
-
filtersAll: string;
|
|
62
|
-
filtersUnresolved: string;
|
|
63
|
-
filtersResolved: string;
|
|
64
|
-
historyHelpText: string;
|
|
65
|
-
historyLoading: string;
|
|
66
|
-
historyLoadFailed: string;
|
|
67
|
-
statusLoadFailed: string;
|
|
68
|
-
emptyAll: string;
|
|
69
|
-
emptyUnresolved: string;
|
|
70
|
-
emptyResolved: string;
|
|
71
|
-
scopeLabel: string;
|
|
72
|
-
scopeMine: string;
|
|
73
|
-
scopeTenant: string;
|
|
74
|
-
reportModeTitle: string;
|
|
75
|
-
reportModeDescription: string;
|
|
76
|
-
reportModeCancelAction: string;
|
|
77
|
-
generalPageTargetLabel: string;
|
|
78
|
-
surfaceRequiredDescription: string;
|
|
79
|
-
specificSectionUnavailableDescription: string;
|
|
80
|
-
createTitlePrefix: string;
|
|
81
|
-
editTitlePrefix: string;
|
|
82
|
-
replyTitlePrefix: string;
|
|
83
|
-
createDescriptionPrefix: string;
|
|
84
|
-
editDescription: string;
|
|
85
|
-
replyDescription: string;
|
|
86
|
-
notePlaceholder: string;
|
|
87
|
-
noteMinimumSuffix: string;
|
|
88
|
-
keyboardShortcutHint: string;
|
|
89
|
-
originalIssueLabel: string;
|
|
90
|
-
cancelAction: string;
|
|
91
|
-
submitAction: string;
|
|
92
|
-
submittingAction: string;
|
|
93
|
-
editAction: string;
|
|
94
|
-
replyAction: string;
|
|
95
|
-
hydrateLoading: string;
|
|
96
|
-
hydrateFailed: string;
|
|
97
|
-
retryAction: string;
|
|
98
|
-
originHumanLabel: string;
|
|
99
|
-
originMachineLabel: string;
|
|
100
|
-
machineOriginFallback: string;
|
|
101
|
-
threadTitle: string;
|
|
102
|
-
threadDescription: string;
|
|
103
|
-
threadLoading: string;
|
|
104
|
-
threadLoadFailed: string;
|
|
105
|
-
threadEmpty: string;
|
|
106
|
-
threadNeedsResponseBadge: string;
|
|
107
|
-
threadAuthorOperator: string;
|
|
108
|
-
threadAuthorReporter: string;
|
|
109
|
-
threadKindClarificationRequest: string;
|
|
110
|
-
threadKindReporterResponse: string;
|
|
111
|
-
threadKindFinalResponse: string;
|
|
112
|
-
threadResponsePlaceholder: string;
|
|
113
|
-
threadResponseLabel: string;
|
|
114
|
-
threadResponseSubmitAction: string;
|
|
115
|
-
threadResponseSubmittingAction: string;
|
|
116
|
-
threadResponseConflict: string;
|
|
117
|
-
threadResponseFailed: string;
|
|
118
|
-
}
|
|
119
|
-
interface IssueReportingProviderProps {
|
|
120
|
-
client: IssueReportingClient;
|
|
121
|
-
isEligible: boolean;
|
|
122
|
-
reporterRoleHint?: string;
|
|
123
|
-
principalId?: string | null;
|
|
124
|
-
getPageUrl?: () => string;
|
|
125
|
-
defaultScope?: IssueReportScope;
|
|
126
|
-
allowTenantScope?: boolean;
|
|
127
|
-
defaultCreateMode?: IssueReportingCreateMode;
|
|
128
|
-
inputModes?: IssueReportingInputMode[];
|
|
129
|
-
defaultInputMode?: IssueReportingInputMode;
|
|
130
|
-
voice?: IssueReportingVoiceConfig;
|
|
131
|
-
copy?: Partial<IssueReportingCopy>;
|
|
132
|
-
children: ReactNode;
|
|
133
|
-
}
|
|
134
|
-
interface FloatingIssueReportButtonProps {
|
|
135
|
-
className?: string;
|
|
136
|
-
positionClassName?: string;
|
|
137
|
-
}
|
|
138
|
-
interface IssueReportingPageConfigProps {
|
|
139
|
-
createMode: IssueReportingCreateMode;
|
|
140
|
-
}
|
|
8
|
+
import * as spaps_types from 'spaps-types';
|
|
9
|
+
import { IssueReport, IssueReportStatus, IssueReportScope, IssueReportMessage, IssueReportingInputMode, ListIssueReportMessagesResponse } from 'spaps-types';
|
|
10
|
+
export { CreateIssueReportRequest, CreateReporterMessageRequest, IssueReport, IssueReportAttachmentOut, IssueReportListResult, IssueReportMessage, IssueReportScope, IssueReportStatus, IssueReportStatusResult, ListIssueReportMessagesResponse, ReplyIssueReportRequest, UpdateIssueReportRequest } from 'spaps-types';
|
|
141
11
|
|
|
142
12
|
declare function IssueReportMessageThread({ issueReportId, }: {
|
|
143
13
|
issueReportId: string;
|
|
@@ -196,6 +66,7 @@ type IssueReportingContextValue = {
|
|
|
196
66
|
voice: Required<Pick<IssueReportingVoiceConfig, "provider" | "modelId" | "requireMicrophonePermission">> & {
|
|
197
67
|
microphone?: IssueReportingVoiceConfig["microphone"];
|
|
198
68
|
};
|
|
69
|
+
screenshotCapture: IssueReportingScreenshotCaptureConfig | undefined;
|
|
199
70
|
needsResponseIssueIds: string[];
|
|
200
71
|
setNeedsResponse: (issueReportId: string, needsResponse: boolean) => void;
|
|
201
72
|
};
|
|
@@ -459,7 +330,7 @@ declare function useIssueReportingMessageMutation(issueReportId: string | null):
|
|
|
459
330
|
body: string;
|
|
460
331
|
idempotency_key?: string;
|
|
461
332
|
}, unknown>;
|
|
462
|
-
declare function IssueReportingProvider({ client, isEligible, reporterRoleHint, principalId, getPageUrl, defaultScope, allowTenantScope, defaultCreateMode, inputModes, defaultInputMode, voice, copy, children, }: IssueReportingProviderProps): react_jsx_runtime.JSX.Element;
|
|
333
|
+
declare function IssueReportingProvider({ client, isEligible, reporterRoleHint, principalId, getPageUrl, defaultScope, allowTenantScope, defaultCreateMode, inputModes, defaultInputMode, voice, screenshotCapture, copy, children, }: IssueReportingProviderProps): react_jsx_runtime.JSX.Element;
|
|
463
334
|
|
|
464
335
|
interface ReportModeContextValue {
|
|
465
336
|
isReportMode: boolean;
|
|
@@ -472,4 +343,4 @@ interface ReportModeContextValue {
|
|
|
472
343
|
declare const ReportModeContext: React.Context<ReportModeContextValue | null>;
|
|
473
344
|
declare function useReportMode(): ReportModeContextValue | null;
|
|
474
345
|
|
|
475
|
-
export { FloatingIssueReportButton,
|
|
346
|
+
export { FloatingIssueReportButton, FloatingIssueReportButtonProps, IssueHistoryFilter, IssueReportMessageThread, IssueReportingCopy, IssueReportingCreateMode, IssueReportingPageConfig, IssueReportingPageConfigProps, IssueReportingProvider, IssueReportingProviderProps, IssueReportingScreenshotCaptureConfig, ReportModeContext, type ReportModeContextValue, ReportableInput, ReportableSection, defaultIssueReportingCopy, filterIssueReports, getEntryPointClassName, getEntryPointState, getIssueNoteLengthMessage, getIssueStatusBadgeLabel, getIssueStatusClassName, isClosedIssueStatus, isOpenIssueStatus, isReporterMessageConflict, issueReportingKeys, selectReporterVisibleMessages, useIssueReporting, useIssueReportingHistory, useIssueReportingMessageMutation, useIssueReportingMessages, useIssueReportingMutations, useIssueReportingStatus, useReportMode };
|