@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.d.cts
CHANGED
|
@@ -29,6 +29,7 @@ type AudioAttachment = Extract<MediaAttachment, {
|
|
|
29
29
|
kind: 'audio';
|
|
30
30
|
}>;
|
|
31
31
|
type VoiceComposerState = 'idle' | 'preparing' | 'waiting_for_speech' | 'listening' | 'finishing' | 'review' | 'sending' | 'error';
|
|
32
|
+
type VoiceReviewMode = 'manual' | 'armed';
|
|
32
33
|
type VoiceTranscriptMode = 'none' | 'final-only' | 'partial-and-final';
|
|
33
34
|
interface VoiceTranscript {
|
|
34
35
|
partial?: string;
|
|
@@ -130,6 +131,8 @@ interface ChatConfig {
|
|
|
130
131
|
voiceFinishing?: string;
|
|
131
132
|
voiceReview?: string;
|
|
132
133
|
voiceSending?: string;
|
|
134
|
+
voiceReviewArmedHint?: string;
|
|
135
|
+
voiceReviewPausedHint?: string;
|
|
133
136
|
voiceStart?: string;
|
|
134
137
|
voiceStop?: string;
|
|
135
138
|
voiceSendNow?: string;
|
|
@@ -202,6 +205,7 @@ interface ChatConfig {
|
|
|
202
205
|
voiceCompose?: {
|
|
203
206
|
enabled?: boolean;
|
|
204
207
|
defaultMode?: 'text' | 'voice';
|
|
208
|
+
reviewMode?: VoiceReviewMode;
|
|
205
209
|
autoSendDelayMs?: number;
|
|
206
210
|
persistComposer?: boolean;
|
|
207
211
|
showTranscriptPreview?: boolean;
|
|
@@ -669,4 +673,4 @@ declare const chatUtils: {
|
|
|
669
673
|
generateThreadTitle: (firstMessage: string) => string;
|
|
670
674
|
};
|
|
671
675
|
|
|
672
|
-
export { type AgentOption, type AudioAttachment, type ChatCallbacks, type ChatConfig, ChatHeader, type ChatHeaderConfig, type ChatHeaderProps, ChatInput, type ChatMessage, type ChatState, type ChatThread, ChatUI, type ChatUserContext, ChatUserContextProvider, type ChatV2Props, type CreateVoiceProvider, type CustomField, type FileUploadProgress, type MediaAttachment, type MemoryItem, Message, type MessageAction, type MessageActionEvent, Sidebar, type SidebarConfig, type SidebarProps, type StateCallback, type StreamingUpdate, ThreadManager, type ToolCall, type UserCustomField, UserMenu, type UserMenuCallbacks, type UserMenuConfig, type UserMenuProps, type UserMenuUser, UserProfile, type UserProfileConfig, type UserProfileProps, type UserProfileUser, type VoiceComposerState, type VoiceProvider, type VoiceProviderHandlers, type VoiceProviderOptions, type VoiceSegment, type VoiceTranscript, type VoiceTranscriptMode, chatConfigPresets, chatUtils, cn, configUtils, createObjectUrlFromDataUrl, defaultChatConfig, featureFlags, formatDate, mergeConfig, themeUtils, useChatUserContext, validateConfig };
|
|
676
|
+
export { type AgentOption, type AudioAttachment, type ChatCallbacks, type ChatConfig, ChatHeader, type ChatHeaderConfig, type ChatHeaderProps, ChatInput, type ChatMessage, type ChatState, type ChatThread, ChatUI, type ChatUserContext, ChatUserContextProvider, type ChatV2Props, type CreateVoiceProvider, type CustomField, type FileUploadProgress, type MediaAttachment, type MemoryItem, Message, type MessageAction, type MessageActionEvent, Sidebar, type SidebarConfig, type SidebarProps, type StateCallback, type StreamingUpdate, ThreadManager, type ToolCall, type UserCustomField, UserMenu, type UserMenuCallbacks, type UserMenuConfig, type UserMenuProps, type UserMenuUser, UserProfile, type UserProfileConfig, type UserProfileProps, type UserProfileUser, type VoiceComposerState, type VoiceProvider, type VoiceProviderHandlers, type VoiceProviderOptions, type VoiceReviewMode, type VoiceSegment, type VoiceTranscript, type VoiceTranscriptMode, chatConfigPresets, chatUtils, cn, configUtils, createObjectUrlFromDataUrl, defaultChatConfig, featureFlags, formatDate, mergeConfig, themeUtils, useChatUserContext, validateConfig };
|
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ type AudioAttachment = Extract<MediaAttachment, {
|
|
|
29
29
|
kind: 'audio';
|
|
30
30
|
}>;
|
|
31
31
|
type VoiceComposerState = 'idle' | 'preparing' | 'waiting_for_speech' | 'listening' | 'finishing' | 'review' | 'sending' | 'error';
|
|
32
|
+
type VoiceReviewMode = 'manual' | 'armed';
|
|
32
33
|
type VoiceTranscriptMode = 'none' | 'final-only' | 'partial-and-final';
|
|
33
34
|
interface VoiceTranscript {
|
|
34
35
|
partial?: string;
|
|
@@ -130,6 +131,8 @@ interface ChatConfig {
|
|
|
130
131
|
voiceFinishing?: string;
|
|
131
132
|
voiceReview?: string;
|
|
132
133
|
voiceSending?: string;
|
|
134
|
+
voiceReviewArmedHint?: string;
|
|
135
|
+
voiceReviewPausedHint?: string;
|
|
133
136
|
voiceStart?: string;
|
|
134
137
|
voiceStop?: string;
|
|
135
138
|
voiceSendNow?: string;
|
|
@@ -202,6 +205,7 @@ interface ChatConfig {
|
|
|
202
205
|
voiceCompose?: {
|
|
203
206
|
enabled?: boolean;
|
|
204
207
|
defaultMode?: 'text' | 'voice';
|
|
208
|
+
reviewMode?: VoiceReviewMode;
|
|
205
209
|
autoSendDelayMs?: number;
|
|
206
210
|
persistComposer?: boolean;
|
|
207
211
|
showTranscriptPreview?: boolean;
|
|
@@ -669,4 +673,4 @@ declare const chatUtils: {
|
|
|
669
673
|
generateThreadTitle: (firstMessage: string) => string;
|
|
670
674
|
};
|
|
671
675
|
|
|
672
|
-
export { type AgentOption, type AudioAttachment, type ChatCallbacks, type ChatConfig, ChatHeader, type ChatHeaderConfig, type ChatHeaderProps, ChatInput, type ChatMessage, type ChatState, type ChatThread, ChatUI, type ChatUserContext, ChatUserContextProvider, type ChatV2Props, type CreateVoiceProvider, type CustomField, type FileUploadProgress, type MediaAttachment, type MemoryItem, Message, type MessageAction, type MessageActionEvent, Sidebar, type SidebarConfig, type SidebarProps, type StateCallback, type StreamingUpdate, ThreadManager, type ToolCall, type UserCustomField, UserMenu, type UserMenuCallbacks, type UserMenuConfig, type UserMenuProps, type UserMenuUser, UserProfile, type UserProfileConfig, type UserProfileProps, type UserProfileUser, type VoiceComposerState, type VoiceProvider, type VoiceProviderHandlers, type VoiceProviderOptions, type VoiceSegment, type VoiceTranscript, type VoiceTranscriptMode, chatConfigPresets, chatUtils, cn, configUtils, createObjectUrlFromDataUrl, defaultChatConfig, featureFlags, formatDate, mergeConfig, themeUtils, useChatUserContext, validateConfig };
|
|
676
|
+
export { type AgentOption, type AudioAttachment, type ChatCallbacks, type ChatConfig, ChatHeader, type ChatHeaderConfig, type ChatHeaderProps, ChatInput, type ChatMessage, type ChatState, type ChatThread, ChatUI, type ChatUserContext, ChatUserContextProvider, type ChatV2Props, type CreateVoiceProvider, type CustomField, type FileUploadProgress, type MediaAttachment, type MemoryItem, Message, type MessageAction, type MessageActionEvent, Sidebar, type SidebarConfig, type SidebarProps, type StateCallback, type StreamingUpdate, ThreadManager, type ToolCall, type UserCustomField, UserMenu, type UserMenuCallbacks, type UserMenuConfig, type UserMenuProps, type UserMenuUser, UserProfile, type UserProfileConfig, type UserProfileProps, type UserProfileUser, type VoiceComposerState, type VoiceProvider, type VoiceProviderHandlers, type VoiceProviderOptions, type VoiceReviewMode, type VoiceSegment, type VoiceTranscript, type VoiceTranscriptMode, chatConfigPresets, chatUtils, cn, configUtils, createObjectUrlFromDataUrl, defaultChatConfig, featureFlags, formatDate, mergeConfig, themeUtils, useChatUserContext, validateConfig };
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,8 @@ var defaultChatConfig = {
|
|
|
40
40
|
voiceFinishing: "Finishing capture...",
|
|
41
41
|
voiceReview: "Ready to send",
|
|
42
42
|
voiceSending: "Sending...",
|
|
43
|
+
voiceReviewArmedHint: "Still listening. Speak to add more before it sends.",
|
|
44
|
+
voiceReviewPausedHint: "Tap the mic to keep adding to this message.",
|
|
43
45
|
voiceStart: "Start recording",
|
|
44
46
|
voiceStop: "Stop recording",
|
|
45
47
|
voiceSendNow: "Send now",
|
|
@@ -115,6 +117,7 @@ var defaultChatConfig = {
|
|
|
115
117
|
voiceCompose: {
|
|
116
118
|
enabled: false,
|
|
117
119
|
defaultMode: "text",
|
|
120
|
+
reviewMode: "manual",
|
|
118
121
|
autoSendDelayMs: 5e3,
|
|
119
122
|
persistComposer: true,
|
|
120
123
|
showTranscriptPreview: true,
|
|
@@ -3240,11 +3243,13 @@ var VoiceComposer = ({
|
|
|
3240
3243
|
countdownMs,
|
|
3241
3244
|
autoSendDelayMs,
|
|
3242
3245
|
isAutoSendActive,
|
|
3246
|
+
reviewMode,
|
|
3243
3247
|
errorMessage,
|
|
3244
3248
|
disabled = false,
|
|
3245
3249
|
labels,
|
|
3246
3250
|
onStart,
|
|
3247
3251
|
onStop,
|
|
3252
|
+
onPauseReview,
|
|
3248
3253
|
onCancelAutoSend,
|
|
3249
3254
|
onDiscard,
|
|
3250
3255
|
onRecordAgain,
|
|
@@ -3253,17 +3258,34 @@ var VoiceComposer = ({
|
|
|
3253
3258
|
}) => {
|
|
3254
3259
|
const transcriptText = resolveTranscriptText(transcript, transcriptMode);
|
|
3255
3260
|
const countdownSeconds = Math.max(1, Math.ceil(countdownMs / 1e3));
|
|
3256
|
-
const countdownValue = autoSendDelayMs > 0 ? Math.min(100, Math.max(0, (autoSendDelayMs - countdownMs) / autoSendDelayMs * 100)) : 100;
|
|
3257
3261
|
const isBusy = state === "preparing" || state === "finishing" || state === "sending";
|
|
3258
3262
|
const isCapturing = state === "waiting_for_speech" || state === "listening";
|
|
3259
|
-
const
|
|
3263
|
+
const hasDraft = Boolean(attachment);
|
|
3264
|
+
const isDraftLayout = hasDraft;
|
|
3265
|
+
const isArmedDraft = isDraftLayout && reviewMode === "armed" && (state === "waiting_for_speech" || state === "listening");
|
|
3266
|
+
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";
|
|
3260
3267
|
const levelValue = isCapturing || state === "preparing" || state === "finishing" ? Math.max(8, Math.round(audioLevel * 100)) : 0;
|
|
3261
|
-
const headerLabel = state === "error" ? labels?.voiceCaptureError || "Unable to capture audio." : resolveStateLabel(state, labels, errorMessage);
|
|
3262
|
-
|
|
3268
|
+
const headerLabel = hasDraft && state !== "sending" && state !== "error" ? draftStatusLabel : state === "error" ? labels?.voiceCaptureError || "Unable to capture audio." : resolveStateLabel(state, labels, errorMessage);
|
|
3269
|
+
const reviewHelperText = isArmedDraft ? labels?.voiceReviewArmedHint || "Speak to add more before it sends." : labels?.voiceReviewPausedHint || labels?.voiceRecordAgain || "Tap the mic to continue this message.";
|
|
3270
|
+
const orbIsListening = state === "listening";
|
|
3271
|
+
const orbCanStop = !isDraftLayout && (state === "waiting_for_speech" || state === "listening");
|
|
3272
|
+
const orbIsReviewBusy = state === "preparing" || state === "finishing" || state === "sending";
|
|
3273
|
+
const handleReviewOrbClick = () => {
|
|
3274
|
+
if (state === "listening") {
|
|
3275
|
+
onStop();
|
|
3276
|
+
return;
|
|
3277
|
+
}
|
|
3278
|
+
if (isArmedDraft) {
|
|
3279
|
+
onPauseReview();
|
|
3280
|
+
return;
|
|
3281
|
+
}
|
|
3282
|
+
onRecordAgain();
|
|
3283
|
+
};
|
|
3284
|
+
return /* @__PURE__ */ jsxs11("div", { className: "w-full max-w-3xl rounded-2xl border bg-background p-3 shadow-sm sm:p-4 md:min-w-3xl", children: [
|
|
3263
3285
|
/* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between gap-2 sm:gap-3", children: [
|
|
3264
3286
|
/* @__PURE__ */ jsxs11("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
3265
3287
|
/* @__PURE__ */ jsx21(Badge, { variant: "outline", children: labels?.voiceTitle || "Voice" }),
|
|
3266
|
-
/* @__PURE__ */ jsx21("span", { className: "truncate text-
|
|
3288
|
+
/* @__PURE__ */ jsx21("span", { className: "truncate rounded-full bg-muted px-2.5 py-1 text-[11px] sm:text-xs text-muted-foreground", children: headerLabel })
|
|
3267
3289
|
] }),
|
|
3268
3290
|
/* @__PURE__ */ jsxs11(
|
|
3269
3291
|
Button,
|
|
@@ -3281,7 +3303,7 @@ var VoiceComposer = ({
|
|
|
3281
3303
|
}
|
|
3282
3304
|
)
|
|
3283
3305
|
] }),
|
|
3284
|
-
!
|
|
3306
|
+
!isDraftLayout ? /* @__PURE__ */ jsx21("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__ */ jsxs11("div", { className: "mx-auto flex w-full max-w-sm flex-col items-center gap-3", children: [
|
|
3285
3307
|
/* @__PURE__ */ jsx21(
|
|
3286
3308
|
Button,
|
|
3287
3309
|
{
|
|
@@ -3303,11 +3325,8 @@ var VoiceComposer = ({
|
|
|
3303
3325
|
] }),
|
|
3304
3326
|
showTranscriptPreview && transcriptMode !== "none" && transcriptText && /* @__PURE__ */ jsx21("div", { className: "w-full rounded-lg border bg-background px-3 py-2 text-left text-sm", children: transcriptText })
|
|
3305
3327
|
] }) }) : /* @__PURE__ */ jsxs11("div", { className: "mt-3 rounded-xl border bg-muted/20 p-3 sm:p-4", children: [
|
|
3306
|
-
/* @__PURE__ */ jsxs11("div", { className: "flex items-
|
|
3307
|
-
/* @__PURE__ */
|
|
3308
|
-
/* @__PURE__ */ jsx21("div", { className: "text-sm font-medium text-foreground", children: labels?.voiceReview || "Ready to send" }),
|
|
3309
|
-
/* @__PURE__ */ jsx21("div", { className: "text-xs text-muted-foreground", children: formatDuration(durationMs) })
|
|
3310
|
-
] }),
|
|
3328
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between gap-2 text-xs text-muted-foreground", children: [
|
|
3329
|
+
/* @__PURE__ */ jsx21("span", { children: formatDuration(durationMs) }),
|
|
3311
3330
|
/* @__PURE__ */ jsx21(
|
|
3312
3331
|
Button,
|
|
3313
3332
|
{
|
|
@@ -3323,31 +3342,39 @@ var VoiceComposer = ({
|
|
|
3323
3342
|
}
|
|
3324
3343
|
)
|
|
3325
3344
|
] }),
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
isAutoSendActive && autoSendDelayMs > 0 && /* @__PURE__ */ jsxs11("div", { className: "mt-3 space-y-2", children: [
|
|
3329
|
-
/* @__PURE__ */ jsx21(Progress, { value: countdownValue, className: "h-2" }),
|
|
3330
|
-
/* @__PURE__ */ jsx21("div", { className: "text-center text-xs text-muted-foreground", children: interpolateSeconds(labels?.voiceAutoSendIn, countdownSeconds) })
|
|
3331
|
-
] }),
|
|
3332
|
-
/* @__PURE__ */ jsxs11("div", { className: "mt-3 flex items-center justify-end gap-2", children: [
|
|
3333
|
-
isAutoSendActive && /* @__PURE__ */ jsxs11(Button, { type: "button", variant: "ghost", size: "sm", onClick: onCancelAutoSend, disabled, children: [
|
|
3334
|
-
/* @__PURE__ */ jsx21(X2, { className: "h-4 w-4" }),
|
|
3335
|
-
labels?.voiceCancel || "Cancel"
|
|
3336
|
-
] }),
|
|
3337
|
-
!isAutoSendActive && /* @__PURE__ */ jsx21(
|
|
3345
|
+
/* @__PURE__ */ jsxs11("div", { className: "mt-4 flex flex-col items-center gap-4 text-center", children: [
|
|
3346
|
+
/* @__PURE__ */ jsx21(
|
|
3338
3347
|
Button,
|
|
3339
3348
|
{
|
|
3340
3349
|
type: "button",
|
|
3341
|
-
variant: "outline",
|
|
3342
3350
|
size: "icon",
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
children: /* @__PURE__ */ jsx21(Mic, { className: "h-
|
|
3351
|
+
variant: orbCanStop ? "destructive" : "outline",
|
|
3352
|
+
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"}`,
|
|
3353
|
+
onClick: handleReviewOrbClick,
|
|
3354
|
+
disabled: disabled || orbIsReviewBusy,
|
|
3355
|
+
children: orbIsReviewBusy ? /* @__PURE__ */ jsx21(Loader2, { className: "h-7 w-7 animate-spin" }) : orbIsListening ? /* @__PURE__ */ jsx21(Square, { className: "h-7 w-7" }) : isArmedDraft ? /* @__PURE__ */ jsx21(Mic, { className: "h-7 w-7 animate-pulse" }) : /* @__PURE__ */ jsx21(Mic, { className: "h-7 w-7" })
|
|
3348
3356
|
}
|
|
3349
3357
|
),
|
|
3350
|
-
/* @__PURE__ */ jsxs11(
|
|
3358
|
+
/* @__PURE__ */ jsxs11("div", { className: "max-w-sm space-y-1 px-2", children: [
|
|
3359
|
+
/* @__PURE__ */ jsx21("p", { className: "text-sm text-foreground", children: reviewHelperText }),
|
|
3360
|
+
isCapturing && /* @__PURE__ */ jsx21("div", { className: "mx-auto h-1.5 w-32 overflow-hidden rounded-full bg-red-100", children: /* @__PURE__ */ jsx21(
|
|
3361
|
+
"div",
|
|
3362
|
+
{
|
|
3363
|
+
className: "h-full rounded-full bg-red-500 transition-[width] duration-150",
|
|
3364
|
+
style: { width: `${levelValue}%` }
|
|
3365
|
+
}
|
|
3366
|
+
) })
|
|
3367
|
+
] })
|
|
3368
|
+
] }),
|
|
3369
|
+
attachment && /* @__PURE__ */ jsx21("div", { className: "mt-4 rounded-lg border bg-background/90 p-2 shadow-sm", children: /* @__PURE__ */ jsx21("audio", { controls: true, preload: "metadata", className: "w-full", children: /* @__PURE__ */ jsx21("source", { src: attachment.dataUrl, type: attachment.mimeType }) }) }),
|
|
3370
|
+
showTranscriptPreview && transcriptMode !== "none" && transcriptText && /* @__PURE__ */ jsx21("div", { className: "mt-3 rounded-lg border bg-background px-3 py-2 text-left text-sm", children: transcriptText }),
|
|
3371
|
+
isAutoSendActive && autoSendDelayMs > 0 && /* @__PURE__ */ jsx21("div", { className: "mt-3 flex justify-center", children: /* @__PURE__ */ jsx21("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) }) }),
|
|
3372
|
+
/* @__PURE__ */ jsxs11("div", { className: "mt-4 grid grid-cols-1 gap-2 sm:flex sm:items-center sm:justify-end", children: [
|
|
3373
|
+
isAutoSendActive && /* @__PURE__ */ jsxs11(Button, { type: "button", variant: "ghost", size: "sm", onClick: onCancelAutoSend, disabled, className: "w-full sm:w-auto", children: [
|
|
3374
|
+
/* @__PURE__ */ jsx21(X2, { className: "h-4 w-4" }),
|
|
3375
|
+
labels?.voiceCancel || "Cancel"
|
|
3376
|
+
] }),
|
|
3377
|
+
/* @__PURE__ */ jsxs11(Button, { type: "button", size: "sm", onClick: onSendNow, disabled, className: "w-full sm:w-auto", children: [
|
|
3351
3378
|
/* @__PURE__ */ jsx21(Send, { className: "h-4 w-4" }),
|
|
3352
3379
|
labels?.voiceSendNow || "Send now"
|
|
3353
3380
|
] })
|
|
@@ -3636,6 +3663,7 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3636
3663
|
}) {
|
|
3637
3664
|
const voiceComposeEnabled = config?.voiceCompose?.enabled === true;
|
|
3638
3665
|
const voiceDefaultMode = config?.voiceCompose?.defaultMode ?? "text";
|
|
3666
|
+
const voiceReviewMode = config?.voiceCompose?.reviewMode ?? "manual";
|
|
3639
3667
|
const voiceAutoSendDelayMs = config?.voiceCompose?.autoSendDelayMs ?? 5e3;
|
|
3640
3668
|
const voicePersistComposer = config?.voiceCompose?.persistComposer ?? true;
|
|
3641
3669
|
const voiceShowTranscriptPreview = config?.voiceCompose?.showTranscriptPreview ?? true;
|
|
@@ -3870,13 +3898,30 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3870
3898
|
setIsVoiceAutoSendActive(false);
|
|
3871
3899
|
setVoiceError(null);
|
|
3872
3900
|
}, []);
|
|
3901
|
+
const armVoiceDraftForAppend = useCallback3((segment) => {
|
|
3902
|
+
voiceAppendBaseRef.current = segment;
|
|
3903
|
+
voiceAppendBaseDurationRef.current = segment ? resolveVoiceSegmentDuration(segment) : 0;
|
|
3904
|
+
}, []);
|
|
3905
|
+
const handleVoiceProviderStateChange = useCallback3((nextState) => {
|
|
3906
|
+
if (voiceReviewMode === "armed" && (nextState === "waiting_for_speech" || nextState === "listening")) {
|
|
3907
|
+
const currentDraft = voiceDraftRef.current;
|
|
3908
|
+
if (currentDraft) {
|
|
3909
|
+
armVoiceDraftForAppend(currentDraft);
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
if (voiceReviewMode === "armed" && nextState === "listening" && voiceDraftRef.current) {
|
|
3913
|
+
setVoiceCountdownMs(voiceAutoSendDelayMs);
|
|
3914
|
+
setIsVoiceAutoSendActive(false);
|
|
3915
|
+
}
|
|
3916
|
+
setVoiceState(nextState);
|
|
3917
|
+
}, [armVoiceDraftForAppend, voiceAutoSendDelayMs, voiceReviewMode]);
|
|
3873
3918
|
const ensureVoiceProvider = useCallback3(async () => {
|
|
3874
3919
|
if (voiceProviderRef.current) {
|
|
3875
3920
|
return voiceProviderRef.current;
|
|
3876
3921
|
}
|
|
3877
3922
|
const createProvider = resolveVoiceProviderFactory(config?.voiceCompose?.createProvider);
|
|
3878
3923
|
const provider = await createProvider({
|
|
3879
|
-
onStateChange:
|
|
3924
|
+
onStateChange: handleVoiceProviderStateChange,
|
|
3880
3925
|
onAudioLevelChange: setVoiceAudioLevel,
|
|
3881
3926
|
onDurationChange: (durationMs) => {
|
|
3882
3927
|
setVoiceDurationMs(voiceAppendBaseDurationRef.current + durationMs);
|
|
@@ -3892,8 +3937,6 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3892
3937
|
const previousSegment = voiceAppendBaseRef.current;
|
|
3893
3938
|
try {
|
|
3894
3939
|
const nextSegment = previousSegment ? await appendVoiceSegments(previousSegment, segment) : segment;
|
|
3895
|
-
voiceAppendBaseRef.current = null;
|
|
3896
|
-
voiceAppendBaseDurationRef.current = 0;
|
|
3897
3940
|
voiceDraftRef.current = nextSegment;
|
|
3898
3941
|
setVoiceDraft(nextSegment);
|
|
3899
3942
|
setVoiceTranscript(nextSegment.transcript ?? clearVoiceTranscript());
|
|
@@ -3902,11 +3945,15 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3902
3945
|
setVoiceCountdownMs(voiceAutoSendDelayMs);
|
|
3903
3946
|
setIsVoiceAutoSendActive(voiceAutoSendDelayMs > 0);
|
|
3904
3947
|
setVoiceError(null);
|
|
3905
|
-
|
|
3948
|
+
if (voiceReviewMode === "armed") {
|
|
3949
|
+
armVoiceDraftForAppend(nextSegment);
|
|
3950
|
+
} else {
|
|
3951
|
+
armVoiceDraftForAppend(null);
|
|
3952
|
+
}
|
|
3953
|
+
setVoiceState((currentState) => voiceReviewMode === "armed" && (currentState === "waiting_for_speech" || currentState === "listening") ? currentState : "review");
|
|
3906
3954
|
} catch (error) {
|
|
3907
3955
|
const resolvedError = resolveVoiceErrorMessage(error, config);
|
|
3908
|
-
|
|
3909
|
-
voiceAppendBaseDurationRef.current = 0;
|
|
3956
|
+
armVoiceDraftForAppend(null);
|
|
3910
3957
|
setVoiceAudioLevel(0);
|
|
3911
3958
|
setVoiceCountdownMs(0);
|
|
3912
3959
|
setIsVoiceAutoSendActive(false);
|
|
@@ -3930,8 +3977,7 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3930
3977
|
},
|
|
3931
3978
|
onError: (error) => {
|
|
3932
3979
|
const previousSegment = voiceAppendBaseRef.current;
|
|
3933
|
-
|
|
3934
|
-
voiceAppendBaseDurationRef.current = 0;
|
|
3980
|
+
armVoiceDraftForAppend(null);
|
|
3935
3981
|
setVoiceError(resolveVoiceErrorMessage(error, config));
|
|
3936
3982
|
setVoiceAudioLevel(0);
|
|
3937
3983
|
setVoiceCountdownMs(0);
|
|
@@ -3955,7 +4001,7 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3955
4001
|
});
|
|
3956
4002
|
voiceProviderRef.current = provider;
|
|
3957
4003
|
return provider;
|
|
3958
|
-
}, [config, voiceAutoSendDelayMs, voiceMaxRecordingMs]);
|
|
4004
|
+
}, [armVoiceDraftForAppend, config, handleVoiceProviderStateChange, voiceAutoSendDelayMs, voiceMaxRecordingMs, voiceReviewMode]);
|
|
3959
4005
|
const closeVoiceComposer = useCallback3(async () => {
|
|
3960
4006
|
voiceAppendBaseRef.current = null;
|
|
3961
4007
|
voiceAppendBaseDurationRef.current = 0;
|
|
@@ -4047,16 +4093,21 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
4047
4093
|
void closeVoiceComposer();
|
|
4048
4094
|
}, [voicePersistComposer, resetVoiceComposerState, closeVoiceComposer]);
|
|
4049
4095
|
const sendVoiceDraft = useCallback3(() => {
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4096
|
+
void (async () => {
|
|
4097
|
+
if (!voiceDraft || disabled || isGenerating) {
|
|
4098
|
+
return;
|
|
4099
|
+
}
|
|
4100
|
+
setVoiceState("sending");
|
|
4101
|
+
setVoiceCountdownMs(0);
|
|
4102
|
+
setIsVoiceAutoSendActive(false);
|
|
4103
|
+
if (voiceProviderRef.current) {
|
|
4104
|
+
await voiceProviderRef.current.cancel();
|
|
4105
|
+
}
|
|
4106
|
+
onSubmit("", [...attachments, voiceDraft.attachment]);
|
|
4107
|
+
onChange("");
|
|
4108
|
+
onAttachmentsChange([]);
|
|
4109
|
+
finalizeVoiceComposerAfterSend();
|
|
4110
|
+
})();
|
|
4060
4111
|
}, [
|
|
4061
4112
|
voiceDraft,
|
|
4062
4113
|
disabled,
|
|
@@ -4068,25 +4119,51 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
4068
4119
|
finalizeVoiceComposerAfterSend
|
|
4069
4120
|
]);
|
|
4070
4121
|
const cancelVoiceAutoSend = useCallback3(() => {
|
|
4122
|
+
void (async () => {
|
|
4123
|
+
if (voiceReviewMode === "armed" && voiceProviderRef.current) {
|
|
4124
|
+
await voiceProviderRef.current.cancel();
|
|
4125
|
+
}
|
|
4126
|
+
armVoiceDraftForAppend(null);
|
|
4127
|
+
setVoiceAudioLevel(0);
|
|
4128
|
+
setVoiceState("review");
|
|
4129
|
+
})();
|
|
4071
4130
|
setVoiceCountdownMs(0);
|
|
4072
4131
|
setIsVoiceAutoSendActive(false);
|
|
4073
|
-
}, []);
|
|
4132
|
+
}, [armVoiceDraftForAppend, voiceReviewMode]);
|
|
4133
|
+
const pauseVoiceReview = useCallback3(async () => {
|
|
4134
|
+
if (voiceState === "listening") {
|
|
4135
|
+
await stopVoiceCapture();
|
|
4136
|
+
return;
|
|
4137
|
+
}
|
|
4138
|
+
if (voiceReviewMode === "armed" && voiceProviderRef.current) {
|
|
4139
|
+
await voiceProviderRef.current.cancel();
|
|
4140
|
+
}
|
|
4141
|
+
armVoiceDraftForAppend(null);
|
|
4142
|
+
setVoiceAudioLevel(0);
|
|
4143
|
+
setVoiceState("review");
|
|
4144
|
+
}, [armVoiceDraftForAppend, stopVoiceCapture, voiceReviewMode, voiceState]);
|
|
4074
4145
|
useEffect9(() => {
|
|
4075
|
-
if (
|
|
4146
|
+
if (!voiceDraft || voiceAutoSendDelayMs <= 0 || !isVoiceAutoSendActive) {
|
|
4147
|
+
return;
|
|
4148
|
+
}
|
|
4149
|
+
const canContinueCounting = voiceState === "review" || voiceReviewMode === "armed" && voiceState === "waiting_for_speech";
|
|
4150
|
+
if (!canContinueCounting) {
|
|
4076
4151
|
return;
|
|
4077
4152
|
}
|
|
4078
|
-
const startedAt = Date.now();
|
|
4079
|
-
setVoiceCountdownMs(voiceAutoSendDelayMs);
|
|
4080
4153
|
const timer = setInterval(() => {
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4154
|
+
setVoiceCountdownMs((previous) => {
|
|
4155
|
+
const remaining = Math.max(0, previous - 100);
|
|
4156
|
+
if (remaining <= 0) {
|
|
4157
|
+
clearInterval(timer);
|
|
4158
|
+
queueMicrotask(() => {
|
|
4159
|
+
sendVoiceDraft();
|
|
4160
|
+
});
|
|
4161
|
+
}
|
|
4162
|
+
return remaining;
|
|
4163
|
+
});
|
|
4087
4164
|
}, 100);
|
|
4088
4165
|
return () => clearInterval(timer);
|
|
4089
|
-
}, [voiceState, voiceDraft, voiceAutoSendDelayMs, isVoiceAutoSendActive, sendVoiceDraft]);
|
|
4166
|
+
}, [voiceState, voiceDraft, voiceReviewMode, voiceAutoSendDelayMs, isVoiceAutoSendActive, sendVoiceDraft]);
|
|
4090
4167
|
const removeAttachment = (index) => {
|
|
4091
4168
|
const newAttachments = attachments.filter((_, i) => i !== index);
|
|
4092
4169
|
onAttachmentsChange(newAttachments);
|
|
@@ -4141,6 +4218,7 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
4141
4218
|
countdownMs: voiceCountdownMs,
|
|
4142
4219
|
autoSendDelayMs: voiceAutoSendDelayMs,
|
|
4143
4220
|
isAutoSendActive: isVoiceAutoSendActive,
|
|
4221
|
+
reviewMode: voiceReviewMode,
|
|
4144
4222
|
errorMessage: voiceError,
|
|
4145
4223
|
disabled: disabled || isGenerating,
|
|
4146
4224
|
labels: config?.labels,
|
|
@@ -4150,6 +4228,9 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
4150
4228
|
onStop: () => {
|
|
4151
4229
|
void stopVoiceCapture();
|
|
4152
4230
|
},
|
|
4231
|
+
onPauseReview: () => {
|
|
4232
|
+
void pauseVoiceReview();
|
|
4233
|
+
},
|
|
4153
4234
|
onCancelAutoSend: () => {
|
|
4154
4235
|
cancelVoiceAutoSend();
|
|
4155
4236
|
},
|