@copilotz/chat-ui 0.1.34 → 0.1.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +141 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +141 -60
- package/dist/index.js.map +1 -1
- package/dist/styles.css +52 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -96,6 +96,8 @@ var defaultChatConfig = {
|
|
|
96
96
|
voiceFinishing: "Finishing capture...",
|
|
97
97
|
voiceReview: "Ready to send",
|
|
98
98
|
voiceSending: "Sending...",
|
|
99
|
+
voiceReviewArmedHint: "Still listening. Speak to add more before it sends.",
|
|
100
|
+
voiceReviewPausedHint: "Tap the mic to keep adding to this message.",
|
|
99
101
|
voiceStart: "Start recording",
|
|
100
102
|
voiceStop: "Stop recording",
|
|
101
103
|
voiceSendNow: "Send now",
|
|
@@ -171,6 +173,7 @@ var defaultChatConfig = {
|
|
|
171
173
|
voiceCompose: {
|
|
172
174
|
enabled: false,
|
|
173
175
|
defaultMode: "text",
|
|
176
|
+
reviewMode: "manual",
|
|
174
177
|
autoSendDelayMs: 5e3,
|
|
175
178
|
persistComposer: true,
|
|
176
179
|
showTranscriptPreview: true,
|
|
@@ -3256,11 +3259,13 @@ var VoiceComposer = ({
|
|
|
3256
3259
|
countdownMs,
|
|
3257
3260
|
autoSendDelayMs,
|
|
3258
3261
|
isAutoSendActive,
|
|
3262
|
+
reviewMode,
|
|
3259
3263
|
errorMessage,
|
|
3260
3264
|
disabled = false,
|
|
3261
3265
|
labels,
|
|
3262
3266
|
onStart,
|
|
3263
3267
|
onStop,
|
|
3268
|
+
onPauseReview,
|
|
3264
3269
|
onCancelAutoSend,
|
|
3265
3270
|
onDiscard,
|
|
3266
3271
|
onRecordAgain,
|
|
@@ -3269,17 +3274,34 @@ var VoiceComposer = ({
|
|
|
3269
3274
|
}) => {
|
|
3270
3275
|
const transcriptText = resolveTranscriptText(transcript, transcriptMode);
|
|
3271
3276
|
const countdownSeconds = Math.max(1, Math.ceil(countdownMs / 1e3));
|
|
3272
|
-
const countdownValue = autoSendDelayMs > 0 ? Math.min(100, Math.max(0, (autoSendDelayMs - countdownMs) / autoSendDelayMs * 100)) : 100;
|
|
3273
3277
|
const isBusy = state === "preparing" || state === "finishing" || state === "sending";
|
|
3274
3278
|
const isCapturing = state === "waiting_for_speech" || state === "listening";
|
|
3275
|
-
const
|
|
3279
|
+
const hasDraft = Boolean(attachment);
|
|
3280
|
+
const isDraftLayout = hasDraft;
|
|
3281
|
+
const isArmedDraft = isDraftLayout && reviewMode === "armed" && (state === "waiting_for_speech" || state === "listening");
|
|
3282
|
+
const draftStatusLabel = state === "listening" ? labels?.voiceListening || "Listening..." : state === "waiting_for_speech" ? labels?.voiceWaiting || "Waiting for speech..." : state === "finishing" ? labels?.voiceFinishing || "Finishing capture..." : state === "sending" ? labels?.voiceSending || "Sending..." : labels?.voiceReview || "Ready to send";
|
|
3276
3283
|
const levelValue = isCapturing || state === "preparing" || state === "finishing" ? Math.max(8, Math.round(audioLevel * 100)) : 0;
|
|
3277
|
-
const headerLabel = state === "error" ? labels?.voiceCaptureError || "Unable to capture audio." : resolveStateLabel(state, labels, errorMessage);
|
|
3278
|
-
|
|
3284
|
+
const headerLabel = hasDraft && state !== "sending" && state !== "error" ? draftStatusLabel : state === "error" ? labels?.voiceCaptureError || "Unable to capture audio." : resolveStateLabel(state, labels, errorMessage);
|
|
3285
|
+
const reviewHelperText = isArmedDraft ? labels?.voiceReviewArmedHint || "Speak to add more before it sends." : labels?.voiceReviewPausedHint || labels?.voiceRecordAgain || "Tap the mic to continue this message.";
|
|
3286
|
+
const orbIsListening = state === "listening";
|
|
3287
|
+
const orbCanStop = !isDraftLayout && (state === "waiting_for_speech" || state === "listening");
|
|
3288
|
+
const orbIsReviewBusy = state === "preparing" || state === "finishing" || state === "sending";
|
|
3289
|
+
const handleReviewOrbClick = () => {
|
|
3290
|
+
if (state === "listening") {
|
|
3291
|
+
onStop();
|
|
3292
|
+
return;
|
|
3293
|
+
}
|
|
3294
|
+
if (isArmedDraft) {
|
|
3295
|
+
onPauseReview();
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
onRecordAgain();
|
|
3299
|
+
};
|
|
3300
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "w-full max-w-3xl rounded-2xl border bg-background p-3 shadow-sm sm:p-4 md:min-w-3xl", children: [
|
|
3279
3301
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center justify-between gap-2 sm:gap-3", children: [
|
|
3280
3302
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
3281
3303
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: "outline", children: labels?.voiceTitle || "Voice" }),
|
|
3282
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "truncate text-
|
|
3304
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "truncate rounded-full bg-muted px-2.5 py-1 text-[11px] sm:text-xs text-muted-foreground", children: headerLabel })
|
|
3283
3305
|
] }),
|
|
3284
3306
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
3285
3307
|
Button,
|
|
@@ -3297,7 +3319,7 @@ var VoiceComposer = ({
|
|
|
3297
3319
|
}
|
|
3298
3320
|
)
|
|
3299
3321
|
] }),
|
|
3300
|
-
!
|
|
3322
|
+
!isDraftLayout ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "mt-3 rounded-xl border border-dashed border-primary/30 bg-primary/5 px-3 py-3 text-center sm:px-4 sm:py-4", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "mx-auto flex w-full max-w-sm flex-col items-center gap-3", children: [
|
|
3301
3323
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
3302
3324
|
Button,
|
|
3303
3325
|
{
|
|
@@ -3319,11 +3341,8 @@ var VoiceComposer = ({
|
|
|
3319
3341
|
] }),
|
|
3320
3342
|
showTranscriptPreview && transcriptMode !== "none" && transcriptText && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "w-full rounded-lg border bg-background px-3 py-2 text-left text-sm", children: transcriptText })
|
|
3321
3343
|
] }) }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "mt-3 rounded-xl border bg-muted/20 p-3 sm:p-4", children: [
|
|
3322
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-
|
|
3323
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.
|
|
3324
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "text-sm font-medium text-foreground", children: labels?.voiceReview || "Ready to send" }),
|
|
3325
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "text-xs text-muted-foreground", children: formatDuration(durationMs) })
|
|
3326
|
-
] }),
|
|
3344
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center justify-between gap-2 text-xs text-muted-foreground", children: [
|
|
3345
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: formatDuration(durationMs) }),
|
|
3327
3346
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
3328
3347
|
Button,
|
|
3329
3348
|
{
|
|
@@ -3339,31 +3358,39 @@ var VoiceComposer = ({
|
|
|
3339
3358
|
}
|
|
3340
3359
|
)
|
|
3341
3360
|
] }),
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
isAutoSendActive && autoSendDelayMs > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "mt-3 space-y-2", children: [
|
|
3345
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Progress, { value: countdownValue, className: "h-2" }),
|
|
3346
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "text-center text-xs text-muted-foreground", children: interpolateSeconds(labels?.voiceAutoSendIn, countdownSeconds) })
|
|
3347
|
-
] }),
|
|
3348
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "mt-3 flex items-center justify-end gap-2", children: [
|
|
3349
|
-
isAutoSendActive && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Button, { type: "button", variant: "ghost", size: "sm", onClick: onCancelAutoSend, disabled, children: [
|
|
3350
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.X, { className: "h-4 w-4" }),
|
|
3351
|
-
labels?.voiceCancel || "Cancel"
|
|
3352
|
-
] }),
|
|
3353
|
-
!isAutoSendActive && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
3361
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "mt-4 flex flex-col items-center gap-4 text-center", children: [
|
|
3362
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
3354
3363
|
Button,
|
|
3355
3364
|
{
|
|
3356
3365
|
type: "button",
|
|
3357
|
-
variant: "outline",
|
|
3358
3366
|
size: "icon",
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Mic, { className: "h-
|
|
3367
|
+
variant: orbCanStop ? "destructive" : "outline",
|
|
3368
|
+
className: `h-20 w-20 rounded-full sm:h-24 sm:w-24 ${orbIsListening ? "border-red-500 bg-red-500 text-white hover:bg-red-600" : isArmedDraft ? "border-red-200 bg-red-50 text-red-600 shadow-[0_0_0_10px_rgba(239,68,68,0.08)] hover:bg-red-100 hover:text-red-700" : "border-red-200 bg-red-50 text-red-600 hover:bg-red-100 hover:text-red-700"}`,
|
|
3369
|
+
onClick: handleReviewOrbClick,
|
|
3370
|
+
disabled: disabled || orbIsReviewBusy,
|
|
3371
|
+
children: orbIsReviewBusy ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Loader2, { className: "h-7 w-7 animate-spin" }) : orbIsListening ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Square, { className: "h-7 w-7" }) : isArmedDraft ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Mic, { className: "h-7 w-7 animate-pulse" }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Mic, { className: "h-7 w-7" })
|
|
3364
3372
|
}
|
|
3365
3373
|
),
|
|
3366
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
3374
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "max-w-sm space-y-1 px-2", children: [
|
|
3375
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-sm text-foreground", children: reviewHelperText }),
|
|
3376
|
+
isCapturing && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "mx-auto h-1.5 w-32 overflow-hidden rounded-full bg-red-100", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
3377
|
+
"div",
|
|
3378
|
+
{
|
|
3379
|
+
className: "h-full rounded-full bg-red-500 transition-[width] duration-150",
|
|
3380
|
+
style: { width: `${levelValue}%` }
|
|
3381
|
+
}
|
|
3382
|
+
) })
|
|
3383
|
+
] })
|
|
3384
|
+
] }),
|
|
3385
|
+
attachment && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "mt-4 rounded-lg border bg-background/90 p-2 shadow-sm", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("audio", { controls: true, preload: "metadata", className: "w-full", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("source", { src: attachment.dataUrl, type: attachment.mimeType }) }) }),
|
|
3386
|
+
showTranscriptPreview && transcriptMode !== "none" && transcriptText && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "mt-3 rounded-lg border bg-background px-3 py-2 text-left text-sm", children: transcriptText }),
|
|
3387
|
+
isAutoSendActive && autoSendDelayMs > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "mt-3 flex justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "inline-flex items-center rounded-full border bg-background px-3 py-1 text-xs text-muted-foreground", children: interpolateSeconds(labels?.voiceAutoSendIn, countdownSeconds) }) }),
|
|
3388
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "mt-4 grid grid-cols-1 gap-2 sm:flex sm:items-center sm:justify-end", children: [
|
|
3389
|
+
isAutoSendActive && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Button, { type: "button", variant: "ghost", size: "sm", onClick: onCancelAutoSend, disabled, className: "w-full sm:w-auto", children: [
|
|
3390
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.X, { className: "h-4 w-4" }),
|
|
3391
|
+
labels?.voiceCancel || "Cancel"
|
|
3392
|
+
] }),
|
|
3393
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Button, { type: "button", size: "sm", onClick: onSendNow, disabled, className: "w-full sm:w-auto", children: [
|
|
3367
3394
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Send, { className: "h-4 w-4" }),
|
|
3368
3395
|
labels?.voiceSendNow || "Send now"
|
|
3369
3396
|
] })
|
|
@@ -3640,6 +3667,7 @@ var ChatInput = (0, import_react5.memo)(function ChatInput2({
|
|
|
3640
3667
|
}) {
|
|
3641
3668
|
const voiceComposeEnabled = config?.voiceCompose?.enabled === true;
|
|
3642
3669
|
const voiceDefaultMode = config?.voiceCompose?.defaultMode ?? "text";
|
|
3670
|
+
const voiceReviewMode = config?.voiceCompose?.reviewMode ?? "manual";
|
|
3643
3671
|
const voiceAutoSendDelayMs = config?.voiceCompose?.autoSendDelayMs ?? 5e3;
|
|
3644
3672
|
const voicePersistComposer = config?.voiceCompose?.persistComposer ?? true;
|
|
3645
3673
|
const voiceShowTranscriptPreview = config?.voiceCompose?.showTranscriptPreview ?? true;
|
|
@@ -3874,13 +3902,30 @@ var ChatInput = (0, import_react5.memo)(function ChatInput2({
|
|
|
3874
3902
|
setIsVoiceAutoSendActive(false);
|
|
3875
3903
|
setVoiceError(null);
|
|
3876
3904
|
}, []);
|
|
3905
|
+
const armVoiceDraftForAppend = (0, import_react5.useCallback)((segment) => {
|
|
3906
|
+
voiceAppendBaseRef.current = segment;
|
|
3907
|
+
voiceAppendBaseDurationRef.current = segment ? resolveVoiceSegmentDuration(segment) : 0;
|
|
3908
|
+
}, []);
|
|
3909
|
+
const handleVoiceProviderStateChange = (0, import_react5.useCallback)((nextState) => {
|
|
3910
|
+
if (voiceReviewMode === "armed" && (nextState === "waiting_for_speech" || nextState === "listening")) {
|
|
3911
|
+
const currentDraft = voiceDraftRef.current;
|
|
3912
|
+
if (currentDraft) {
|
|
3913
|
+
armVoiceDraftForAppend(currentDraft);
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
if (voiceReviewMode === "armed" && nextState === "listening" && voiceDraftRef.current) {
|
|
3917
|
+
setVoiceCountdownMs(voiceAutoSendDelayMs);
|
|
3918
|
+
setIsVoiceAutoSendActive(false);
|
|
3919
|
+
}
|
|
3920
|
+
setVoiceState(nextState);
|
|
3921
|
+
}, [armVoiceDraftForAppend, voiceAutoSendDelayMs, voiceReviewMode]);
|
|
3877
3922
|
const ensureVoiceProvider = (0, import_react5.useCallback)(async () => {
|
|
3878
3923
|
if (voiceProviderRef.current) {
|
|
3879
3924
|
return voiceProviderRef.current;
|
|
3880
3925
|
}
|
|
3881
3926
|
const createProvider = resolveVoiceProviderFactory(config?.voiceCompose?.createProvider);
|
|
3882
3927
|
const provider = await createProvider({
|
|
3883
|
-
onStateChange:
|
|
3928
|
+
onStateChange: handleVoiceProviderStateChange,
|
|
3884
3929
|
onAudioLevelChange: setVoiceAudioLevel,
|
|
3885
3930
|
onDurationChange: (durationMs) => {
|
|
3886
3931
|
setVoiceDurationMs(voiceAppendBaseDurationRef.current + durationMs);
|
|
@@ -3896,8 +3941,6 @@ var ChatInput = (0, import_react5.memo)(function ChatInput2({
|
|
|
3896
3941
|
const previousSegment = voiceAppendBaseRef.current;
|
|
3897
3942
|
try {
|
|
3898
3943
|
const nextSegment = previousSegment ? await appendVoiceSegments(previousSegment, segment) : segment;
|
|
3899
|
-
voiceAppendBaseRef.current = null;
|
|
3900
|
-
voiceAppendBaseDurationRef.current = 0;
|
|
3901
3944
|
voiceDraftRef.current = nextSegment;
|
|
3902
3945
|
setVoiceDraft(nextSegment);
|
|
3903
3946
|
setVoiceTranscript(nextSegment.transcript ?? clearVoiceTranscript());
|
|
@@ -3906,11 +3949,15 @@ var ChatInput = (0, import_react5.memo)(function ChatInput2({
|
|
|
3906
3949
|
setVoiceCountdownMs(voiceAutoSendDelayMs);
|
|
3907
3950
|
setIsVoiceAutoSendActive(voiceAutoSendDelayMs > 0);
|
|
3908
3951
|
setVoiceError(null);
|
|
3909
|
-
|
|
3952
|
+
if (voiceReviewMode === "armed") {
|
|
3953
|
+
armVoiceDraftForAppend(nextSegment);
|
|
3954
|
+
} else {
|
|
3955
|
+
armVoiceDraftForAppend(null);
|
|
3956
|
+
}
|
|
3957
|
+
setVoiceState((currentState) => voiceReviewMode === "armed" && (currentState === "waiting_for_speech" || currentState === "listening") ? currentState : "review");
|
|
3910
3958
|
} catch (error) {
|
|
3911
3959
|
const resolvedError = resolveVoiceErrorMessage(error, config);
|
|
3912
|
-
|
|
3913
|
-
voiceAppendBaseDurationRef.current = 0;
|
|
3960
|
+
armVoiceDraftForAppend(null);
|
|
3914
3961
|
setVoiceAudioLevel(0);
|
|
3915
3962
|
setVoiceCountdownMs(0);
|
|
3916
3963
|
setIsVoiceAutoSendActive(false);
|
|
@@ -3934,8 +3981,7 @@ var ChatInput = (0, import_react5.memo)(function ChatInput2({
|
|
|
3934
3981
|
},
|
|
3935
3982
|
onError: (error) => {
|
|
3936
3983
|
const previousSegment = voiceAppendBaseRef.current;
|
|
3937
|
-
|
|
3938
|
-
voiceAppendBaseDurationRef.current = 0;
|
|
3984
|
+
armVoiceDraftForAppend(null);
|
|
3939
3985
|
setVoiceError(resolveVoiceErrorMessage(error, config));
|
|
3940
3986
|
setVoiceAudioLevel(0);
|
|
3941
3987
|
setVoiceCountdownMs(0);
|
|
@@ -3959,7 +4005,7 @@ var ChatInput = (0, import_react5.memo)(function ChatInput2({
|
|
|
3959
4005
|
});
|
|
3960
4006
|
voiceProviderRef.current = provider;
|
|
3961
4007
|
return provider;
|
|
3962
|
-
}, [config, voiceAutoSendDelayMs, voiceMaxRecordingMs]);
|
|
4008
|
+
}, [armVoiceDraftForAppend, config, handleVoiceProviderStateChange, voiceAutoSendDelayMs, voiceMaxRecordingMs, voiceReviewMode]);
|
|
3963
4009
|
const closeVoiceComposer = (0, import_react5.useCallback)(async () => {
|
|
3964
4010
|
voiceAppendBaseRef.current = null;
|
|
3965
4011
|
voiceAppendBaseDurationRef.current = 0;
|
|
@@ -4051,16 +4097,21 @@ var ChatInput = (0, import_react5.memo)(function ChatInput2({
|
|
|
4051
4097
|
void closeVoiceComposer();
|
|
4052
4098
|
}, [voicePersistComposer, resetVoiceComposerState, closeVoiceComposer]);
|
|
4053
4099
|
const sendVoiceDraft = (0, import_react5.useCallback)(() => {
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4100
|
+
void (async () => {
|
|
4101
|
+
if (!voiceDraft || disabled || isGenerating) {
|
|
4102
|
+
return;
|
|
4103
|
+
}
|
|
4104
|
+
setVoiceState("sending");
|
|
4105
|
+
setVoiceCountdownMs(0);
|
|
4106
|
+
setIsVoiceAutoSendActive(false);
|
|
4107
|
+
if (voiceProviderRef.current) {
|
|
4108
|
+
await voiceProviderRef.current.cancel();
|
|
4109
|
+
}
|
|
4110
|
+
onSubmit("", [...attachments, voiceDraft.attachment]);
|
|
4111
|
+
onChange("");
|
|
4112
|
+
onAttachmentsChange([]);
|
|
4113
|
+
finalizeVoiceComposerAfterSend();
|
|
4114
|
+
})();
|
|
4064
4115
|
}, [
|
|
4065
4116
|
voiceDraft,
|
|
4066
4117
|
disabled,
|
|
@@ -4072,25 +4123,51 @@ var ChatInput = (0, import_react5.memo)(function ChatInput2({
|
|
|
4072
4123
|
finalizeVoiceComposerAfterSend
|
|
4073
4124
|
]);
|
|
4074
4125
|
const cancelVoiceAutoSend = (0, import_react5.useCallback)(() => {
|
|
4126
|
+
void (async () => {
|
|
4127
|
+
if (voiceReviewMode === "armed" && voiceProviderRef.current) {
|
|
4128
|
+
await voiceProviderRef.current.cancel();
|
|
4129
|
+
}
|
|
4130
|
+
armVoiceDraftForAppend(null);
|
|
4131
|
+
setVoiceAudioLevel(0);
|
|
4132
|
+
setVoiceState("review");
|
|
4133
|
+
})();
|
|
4075
4134
|
setVoiceCountdownMs(0);
|
|
4076
4135
|
setIsVoiceAutoSendActive(false);
|
|
4077
|
-
}, []);
|
|
4136
|
+
}, [armVoiceDraftForAppend, voiceReviewMode]);
|
|
4137
|
+
const pauseVoiceReview = (0, import_react5.useCallback)(async () => {
|
|
4138
|
+
if (voiceState === "listening") {
|
|
4139
|
+
await stopVoiceCapture();
|
|
4140
|
+
return;
|
|
4141
|
+
}
|
|
4142
|
+
if (voiceReviewMode === "armed" && voiceProviderRef.current) {
|
|
4143
|
+
await voiceProviderRef.current.cancel();
|
|
4144
|
+
}
|
|
4145
|
+
armVoiceDraftForAppend(null);
|
|
4146
|
+
setVoiceAudioLevel(0);
|
|
4147
|
+
setVoiceState("review");
|
|
4148
|
+
}, [armVoiceDraftForAppend, stopVoiceCapture, voiceReviewMode, voiceState]);
|
|
4078
4149
|
(0, import_react5.useEffect)(() => {
|
|
4079
|
-
if (
|
|
4150
|
+
if (!voiceDraft || voiceAutoSendDelayMs <= 0 || !isVoiceAutoSendActive) {
|
|
4151
|
+
return;
|
|
4152
|
+
}
|
|
4153
|
+
const canContinueCounting = voiceState === "review" || voiceReviewMode === "armed" && voiceState === "waiting_for_speech";
|
|
4154
|
+
if (!canContinueCounting) {
|
|
4080
4155
|
return;
|
|
4081
4156
|
}
|
|
4082
|
-
const startedAt = Date.now();
|
|
4083
|
-
setVoiceCountdownMs(voiceAutoSendDelayMs);
|
|
4084
4157
|
const timer = setInterval(() => {
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4158
|
+
setVoiceCountdownMs((previous) => {
|
|
4159
|
+
const remaining = Math.max(0, previous - 100);
|
|
4160
|
+
if (remaining <= 0) {
|
|
4161
|
+
clearInterval(timer);
|
|
4162
|
+
queueMicrotask(() => {
|
|
4163
|
+
sendVoiceDraft();
|
|
4164
|
+
});
|
|
4165
|
+
}
|
|
4166
|
+
return remaining;
|
|
4167
|
+
});
|
|
4091
4168
|
}, 100);
|
|
4092
4169
|
return () => clearInterval(timer);
|
|
4093
|
-
}, [voiceState, voiceDraft, voiceAutoSendDelayMs, isVoiceAutoSendActive, sendVoiceDraft]);
|
|
4170
|
+
}, [voiceState, voiceDraft, voiceReviewMode, voiceAutoSendDelayMs, isVoiceAutoSendActive, sendVoiceDraft]);
|
|
4094
4171
|
const removeAttachment = (index) => {
|
|
4095
4172
|
const newAttachments = attachments.filter((_, i) => i !== index);
|
|
4096
4173
|
onAttachmentsChange(newAttachments);
|
|
@@ -4145,6 +4222,7 @@ var ChatInput = (0, import_react5.memo)(function ChatInput2({
|
|
|
4145
4222
|
countdownMs: voiceCountdownMs,
|
|
4146
4223
|
autoSendDelayMs: voiceAutoSendDelayMs,
|
|
4147
4224
|
isAutoSendActive: isVoiceAutoSendActive,
|
|
4225
|
+
reviewMode: voiceReviewMode,
|
|
4148
4226
|
errorMessage: voiceError,
|
|
4149
4227
|
disabled: disabled || isGenerating,
|
|
4150
4228
|
labels: config?.labels,
|
|
@@ -4154,6 +4232,9 @@ var ChatInput = (0, import_react5.memo)(function ChatInput2({
|
|
|
4154
4232
|
onStop: () => {
|
|
4155
4233
|
void stopVoiceCapture();
|
|
4156
4234
|
},
|
|
4235
|
+
onPauseReview: () => {
|
|
4236
|
+
void pauseVoiceReview();
|
|
4237
|
+
},
|
|
4157
4238
|
onCancelAutoSend: () => {
|
|
4158
4239
|
cancelVoiceAutoSend();
|
|
4159
4240
|
},
|