@copilotz/chat-ui 0.1.32 → 0.1.34
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 +347 -94
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +362 -109
- package/dist/index.js.map +1 -1
- package/dist/styles.css +86 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -32,17 +32,20 @@ var defaultChatConfig = {
|
|
|
32
32
|
recordAudioTooltip: "Record audio",
|
|
33
33
|
voiceEnter: "Voice input",
|
|
34
34
|
voiceExit: "Use keyboard",
|
|
35
|
-
voiceTitle: "Voice
|
|
35
|
+
voiceTitle: "Voice",
|
|
36
|
+
voiceIdle: "Tap the mic to record",
|
|
36
37
|
voicePreparing: "Preparing microphone...",
|
|
37
38
|
voiceWaiting: "Waiting for speech...",
|
|
38
39
|
voiceListening: "Listening...",
|
|
39
40
|
voiceFinishing: "Finishing capture...",
|
|
40
41
|
voiceReview: "Ready to send",
|
|
42
|
+
voiceSending: "Sending...",
|
|
41
43
|
voiceStart: "Start recording",
|
|
42
44
|
voiceStop: "Stop recording",
|
|
43
45
|
voiceSendNow: "Send now",
|
|
44
46
|
voiceCancel: "Cancel",
|
|
45
|
-
|
|
47
|
+
voiceDiscard: "Delete recording",
|
|
48
|
+
voiceRecordAgain: "Continue recording",
|
|
46
49
|
voiceAutoSendIn: "Auto-sends in {{seconds}}s",
|
|
47
50
|
voiceTranscriptPending: "Transcript unavailable",
|
|
48
51
|
voicePermissionDenied: "Microphone access was denied.",
|
|
@@ -111,6 +114,7 @@ var defaultChatConfig = {
|
|
|
111
114
|
},
|
|
112
115
|
voiceCompose: {
|
|
113
116
|
enabled: false,
|
|
117
|
+
defaultMode: "text",
|
|
114
118
|
autoSendDelayMs: 5e3,
|
|
115
119
|
persistComposer: true,
|
|
116
120
|
showTranscriptPreview: true,
|
|
@@ -2846,6 +2850,121 @@ var blobToDataUrl = (blob) => new Promise((resolve, reject) => {
|
|
|
2846
2850
|
reader.onerror = () => reject(reader.error ?? new Error("Failed to read recorded audio"));
|
|
2847
2851
|
reader.readAsDataURL(blob);
|
|
2848
2852
|
});
|
|
2853
|
+
var joinTranscriptParts = (...parts) => {
|
|
2854
|
+
const value = parts.map((part) => part?.trim()).filter((part) => Boolean(part && part.length > 0)).join(" ").trim();
|
|
2855
|
+
return value.length > 0 ? value : void 0;
|
|
2856
|
+
};
|
|
2857
|
+
var getAudioContextCtor = () => globalThis.AudioContext || globalThis.webkitAudioContext;
|
|
2858
|
+
var getOfflineAudioContextCtor = () => globalThis.OfflineAudioContext || globalThis.webkitOfflineAudioContext;
|
|
2859
|
+
var attachmentToArrayBuffer = async (attachment) => {
|
|
2860
|
+
const response = await fetch(attachment.dataUrl);
|
|
2861
|
+
return response.arrayBuffer();
|
|
2862
|
+
};
|
|
2863
|
+
var decodeAudioAttachment = async (attachment) => {
|
|
2864
|
+
const AudioContextCtor = getAudioContextCtor();
|
|
2865
|
+
if (!AudioContextCtor) {
|
|
2866
|
+
throw new Error("Audio decoding is not supported in this browser");
|
|
2867
|
+
}
|
|
2868
|
+
const audioContext = new AudioContextCtor();
|
|
2869
|
+
try {
|
|
2870
|
+
const arrayBuffer = await attachmentToArrayBuffer(attachment);
|
|
2871
|
+
return await audioContext.decodeAudioData(arrayBuffer.slice(0));
|
|
2872
|
+
} finally {
|
|
2873
|
+
await closeAudioContext(audioContext);
|
|
2874
|
+
}
|
|
2875
|
+
};
|
|
2876
|
+
var renderMergedBuffer = async (buffers) => {
|
|
2877
|
+
const OfflineAudioContextCtor = getOfflineAudioContextCtor();
|
|
2878
|
+
if (!OfflineAudioContextCtor) {
|
|
2879
|
+
throw new Error("Offline audio rendering is not supported in this browser");
|
|
2880
|
+
}
|
|
2881
|
+
const numberOfChannels = Math.max(...buffers.map((buffer) => buffer.numberOfChannels));
|
|
2882
|
+
const sampleRate = Math.max(...buffers.map((buffer) => buffer.sampleRate));
|
|
2883
|
+
const totalFrames = Math.max(1, Math.ceil(buffers.reduce((sum, buffer) => sum + buffer.duration * sampleRate, 0)));
|
|
2884
|
+
const offlineContext = new OfflineAudioContextCtor(numberOfChannels, totalFrames, sampleRate);
|
|
2885
|
+
let offsetSeconds = 0;
|
|
2886
|
+
for (const buffer of buffers) {
|
|
2887
|
+
const source = offlineContext.createBufferSource();
|
|
2888
|
+
source.buffer = buffer;
|
|
2889
|
+
source.connect(offlineContext.destination);
|
|
2890
|
+
source.start(offsetSeconds);
|
|
2891
|
+
offsetSeconds += buffer.duration;
|
|
2892
|
+
}
|
|
2893
|
+
return offlineContext.startRendering();
|
|
2894
|
+
};
|
|
2895
|
+
var encodeWav = (audioBuffer) => {
|
|
2896
|
+
const numberOfChannels = audioBuffer.numberOfChannels;
|
|
2897
|
+
const sampleRate = audioBuffer.sampleRate;
|
|
2898
|
+
const bitsPerSample = 16;
|
|
2899
|
+
const bytesPerSample = bitsPerSample / 8;
|
|
2900
|
+
const dataLength = audioBuffer.length * numberOfChannels * bytesPerSample;
|
|
2901
|
+
const buffer = new ArrayBuffer(44 + dataLength);
|
|
2902
|
+
const view = new DataView(buffer);
|
|
2903
|
+
const writeString = (offset2, value) => {
|
|
2904
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
2905
|
+
view.setUint8(offset2 + index, value.charCodeAt(index));
|
|
2906
|
+
}
|
|
2907
|
+
};
|
|
2908
|
+
writeString(0, "RIFF");
|
|
2909
|
+
view.setUint32(4, 36 + dataLength, true);
|
|
2910
|
+
writeString(8, "WAVE");
|
|
2911
|
+
writeString(12, "fmt ");
|
|
2912
|
+
view.setUint32(16, 16, true);
|
|
2913
|
+
view.setUint16(20, 1, true);
|
|
2914
|
+
view.setUint16(22, numberOfChannels, true);
|
|
2915
|
+
view.setUint32(24, sampleRate, true);
|
|
2916
|
+
view.setUint32(28, sampleRate * numberOfChannels * bytesPerSample, true);
|
|
2917
|
+
view.setUint16(32, numberOfChannels * bytesPerSample, true);
|
|
2918
|
+
view.setUint16(34, bitsPerSample, true);
|
|
2919
|
+
writeString(36, "data");
|
|
2920
|
+
view.setUint32(40, dataLength, true);
|
|
2921
|
+
let offset = 44;
|
|
2922
|
+
const channelData = Array.from({ length: numberOfChannels }, (_, index) => audioBuffer.getChannelData(index));
|
|
2923
|
+
for (let sampleIndex = 0; sampleIndex < audioBuffer.length; sampleIndex += 1) {
|
|
2924
|
+
for (let channelIndex = 0; channelIndex < numberOfChannels; channelIndex += 1) {
|
|
2925
|
+
const sample = Math.max(-1, Math.min(1, channelData[channelIndex][sampleIndex]));
|
|
2926
|
+
const pcmValue = sample < 0 ? sample * 32768 : sample * 32767;
|
|
2927
|
+
view.setInt16(offset, pcmValue, true);
|
|
2928
|
+
offset += 2;
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
return new Blob([buffer], { type: "audio/wav" });
|
|
2932
|
+
};
|
|
2933
|
+
var resolveSegmentCount = (segment) => {
|
|
2934
|
+
const candidate = segment?.metadata?.segmentCount;
|
|
2935
|
+
return typeof candidate === "number" && Number.isFinite(candidate) && candidate > 0 ? candidate : segment ? 1 : 0;
|
|
2936
|
+
};
|
|
2937
|
+
var mergeVoiceTranscripts = (previous, incoming) => ({
|
|
2938
|
+
final: joinTranscriptParts(previous?.final, incoming?.final),
|
|
2939
|
+
partial: joinTranscriptParts(previous?.final, incoming?.partial)
|
|
2940
|
+
});
|
|
2941
|
+
var appendVoiceSegments = async (previous, incoming) => {
|
|
2942
|
+
const [previousBuffer, incomingBuffer] = await Promise.all([
|
|
2943
|
+
decodeAudioAttachment(previous.attachment),
|
|
2944
|
+
decodeAudioAttachment(incoming.attachment)
|
|
2945
|
+
]);
|
|
2946
|
+
const mergedBuffer = await renderMergedBuffer([previousBuffer, incomingBuffer]);
|
|
2947
|
+
const mergedBlob = encodeWav(mergedBuffer);
|
|
2948
|
+
const dataUrl = await blobToDataUrl(mergedBlob);
|
|
2949
|
+
const segmentCount = resolveSegmentCount(previous) + resolveSegmentCount(incoming);
|
|
2950
|
+
return {
|
|
2951
|
+
attachment: {
|
|
2952
|
+
kind: "audio",
|
|
2953
|
+
dataUrl,
|
|
2954
|
+
mimeType: mergedBlob.type,
|
|
2955
|
+
durationMs: Math.round(mergedBuffer.duration * 1e3),
|
|
2956
|
+
fileName: `voice-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.wav`,
|
|
2957
|
+
size: mergedBlob.size
|
|
2958
|
+
},
|
|
2959
|
+
transcript: mergeVoiceTranscripts(previous.transcript, incoming.transcript),
|
|
2960
|
+
metadata: {
|
|
2961
|
+
...previous.metadata,
|
|
2962
|
+
...incoming.metadata,
|
|
2963
|
+
segmentCount,
|
|
2964
|
+
source: segmentCount > 1 ? "merged" : incoming.metadata?.source ?? previous.metadata?.source
|
|
2965
|
+
}
|
|
2966
|
+
};
|
|
2967
|
+
};
|
|
2849
2968
|
var stopStream = (stream) => {
|
|
2850
2969
|
if (!stream) return;
|
|
2851
2970
|
stream.getTracks().forEach((track) => track.stop());
|
|
@@ -2967,7 +3086,7 @@ var createManualVoiceProvider = async (handlers, options = {}) => {
|
|
|
2967
3086
|
fileName: `voice-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.webm`,
|
|
2968
3087
|
size: blob.size
|
|
2969
3088
|
},
|
|
2970
|
-
metadata: { source: "manual" }
|
|
3089
|
+
metadata: { source: "manual", segmentCount: 1 }
|
|
2971
3090
|
});
|
|
2972
3091
|
} else {
|
|
2973
3092
|
handlers.onStateChange?.("idle");
|
|
@@ -3063,8 +3182,8 @@ function Progress({
|
|
|
3063
3182
|
}
|
|
3064
3183
|
|
|
3065
3184
|
// src/components/chat/VoiceComposer.tsx
|
|
3066
|
-
import { Keyboard, Loader2, Mic,
|
|
3067
|
-
import {
|
|
3185
|
+
import { Keyboard, Loader2, Mic, Send, Square, Trash2 as Trash23, X as X2 } from "lucide-react";
|
|
3186
|
+
import { jsx as jsx21, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3068
3187
|
var formatDuration = (durationMs) => {
|
|
3069
3188
|
const totalSeconds = Math.max(0, Math.floor(durationMs / 1e3));
|
|
3070
3189
|
const minutes = Math.floor(totalSeconds / 60);
|
|
@@ -3093,12 +3212,12 @@ var resolveStateLabel = (state, labels, errorMessage) => {
|
|
|
3093
3212
|
case "review":
|
|
3094
3213
|
return labels?.voiceReview || "Ready to send";
|
|
3095
3214
|
case "sending":
|
|
3096
|
-
return "Sending...";
|
|
3215
|
+
return labels?.voiceSending || "Sending...";
|
|
3097
3216
|
case "error":
|
|
3098
3217
|
return errorMessage || labels?.voiceCaptureError || "Unable to capture audio.";
|
|
3099
3218
|
case "idle":
|
|
3100
3219
|
default:
|
|
3101
|
-
return labels?.
|
|
3220
|
+
return labels?.voiceIdle || "Tap the mic to record";
|
|
3102
3221
|
}
|
|
3103
3222
|
};
|
|
3104
3223
|
var resolveTranscriptText = (transcript, transcriptMode) => {
|
|
@@ -3115,18 +3234,21 @@ var VoiceComposer = ({
|
|
|
3115
3234
|
transcript,
|
|
3116
3235
|
transcriptMode,
|
|
3117
3236
|
showTranscriptPreview,
|
|
3237
|
+
attachment,
|
|
3118
3238
|
durationMs,
|
|
3119
3239
|
audioLevel,
|
|
3120
3240
|
countdownMs,
|
|
3121
3241
|
autoSendDelayMs,
|
|
3242
|
+
isAutoSendActive,
|
|
3122
3243
|
errorMessage,
|
|
3123
3244
|
disabled = false,
|
|
3124
3245
|
labels,
|
|
3125
3246
|
onStart,
|
|
3126
3247
|
onStop,
|
|
3127
|
-
|
|
3128
|
-
|
|
3248
|
+
onCancelAutoSend,
|
|
3249
|
+
onDiscard,
|
|
3129
3250
|
onRecordAgain,
|
|
3251
|
+
onSendNow,
|
|
3130
3252
|
onExit
|
|
3131
3253
|
}) => {
|
|
3132
3254
|
const transcriptText = resolveTranscriptText(transcript, transcriptMode);
|
|
@@ -3134,12 +3256,14 @@ var VoiceComposer = ({
|
|
|
3134
3256
|
const countdownValue = autoSendDelayMs > 0 ? Math.min(100, Math.max(0, (autoSendDelayMs - countdownMs) / autoSendDelayMs * 100)) : 100;
|
|
3135
3257
|
const isBusy = state === "preparing" || state === "finishing" || state === "sending";
|
|
3136
3258
|
const isCapturing = state === "waiting_for_speech" || state === "listening";
|
|
3259
|
+
const isReviewing = state === "review";
|
|
3137
3260
|
const levelValue = isCapturing || state === "preparing" || state === "finishing" ? Math.max(8, Math.round(audioLevel * 100)) : 0;
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
/* @__PURE__ */ jsx21(
|
|
3261
|
+
const headerLabel = state === "error" ? labels?.voiceCaptureError || "Unable to capture audio." : resolveStateLabel(state, labels, errorMessage);
|
|
3262
|
+
return /* @__PURE__ */ jsxs11("div", { className: "w-full max-w-3xl rounded-xl border bg-background p-3 shadow-sm sm:p-4 md:min-w-3xl", children: [
|
|
3263
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between gap-2 sm:gap-3", children: [
|
|
3264
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
3265
|
+
/* @__PURE__ */ jsx21(Badge, { variant: "outline", children: labels?.voiceTitle || "Voice" }),
|
|
3266
|
+
/* @__PURE__ */ jsx21("span", { className: "truncate text-xs sm:text-sm text-muted-foreground", children: headerLabel })
|
|
3143
3267
|
] }),
|
|
3144
3268
|
/* @__PURE__ */ jsxs11(
|
|
3145
3269
|
Button,
|
|
@@ -3147,71 +3271,89 @@ var VoiceComposer = ({
|
|
|
3147
3271
|
type: "button",
|
|
3148
3272
|
variant: "ghost",
|
|
3149
3273
|
size: "sm",
|
|
3274
|
+
className: "shrink-0 px-2 sm:px-3",
|
|
3150
3275
|
onClick: onExit,
|
|
3151
3276
|
disabled: disabled || isBusy,
|
|
3152
3277
|
children: [
|
|
3153
3278
|
/* @__PURE__ */ jsx21(Keyboard, { className: "h-4 w-4" }),
|
|
3154
|
-
labels?.voiceExit || "Use keyboard"
|
|
3279
|
+
/* @__PURE__ */ jsx21("span", { className: "hidden sm:inline", children: labels?.voiceExit || "Use keyboard" })
|
|
3155
3280
|
]
|
|
3156
3281
|
}
|
|
3157
3282
|
)
|
|
3158
3283
|
] }),
|
|
3159
|
-
/* @__PURE__ */
|
|
3160
|
-
/* @__PURE__ */ jsx21(
|
|
3161
|
-
|
|
3284
|
+
!isReviewing ? /* @__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
|
+
/* @__PURE__ */ jsx21(
|
|
3286
|
+
Button,
|
|
3287
|
+
{
|
|
3288
|
+
type: "button",
|
|
3289
|
+
size: "icon",
|
|
3290
|
+
variant: isCapturing ? "destructive" : "outline",
|
|
3291
|
+
className: `h-16 w-16 rounded-full sm:h-20 sm:w-20 ${isCapturing ? "bg-red-500 hover:bg-red-600 text-white border-red-500" : "border-red-200 bg-red-50 text-red-600 hover:bg-red-100 hover:text-red-700"}`,
|
|
3292
|
+
onClick: isCapturing ? onStop : onStart,
|
|
3293
|
+
disabled: disabled || isBusy,
|
|
3294
|
+
children: isBusy ? /* @__PURE__ */ jsx21(Loader2, { className: "h-7 w-7 animate-spin" }) : isCapturing ? /* @__PURE__ */ jsx21(Square, { className: "h-7 w-7" }) : /* @__PURE__ */ jsx21(Mic, { className: "h-7 w-7" })
|
|
3295
|
+
}
|
|
3296
|
+
),
|
|
3297
|
+
/* @__PURE__ */ jsxs11("div", { className: "w-full space-y-2", children: [
|
|
3162
3298
|
/* @__PURE__ */ jsx21(Progress, { value: levelValue, className: "h-2" }),
|
|
3163
3299
|
/* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [
|
|
3164
3300
|
/* @__PURE__ */ jsx21("span", { children: formatDuration(durationMs) }),
|
|
3165
|
-
/* @__PURE__ */ jsx21("span", { children:
|
|
3301
|
+
/* @__PURE__ */ jsx21("span", { children: isCapturing ? labels?.voiceStop || "Stop recording" : labels?.voiceStart || "Start recording" })
|
|
3166
3302
|
] })
|
|
3167
3303
|
] }),
|
|
3168
|
-
showTranscriptPreview && transcriptMode !== "none" && transcriptText && /* @__PURE__ */ jsx21("div", { className: "w-full
|
|
3169
|
-
] }),
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
state === "error" && errorMessage && /* @__PURE__ */ jsx21("div", { className: "mt-4 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2 text-sm text-destructive", children: errorMessage }),
|
|
3175
|
-
/* @__PURE__ */ jsxs11("div", { className: "mt-4 flex flex-wrap items-center justify-center gap-2", children: [
|
|
3176
|
-
state === "idle" && /* @__PURE__ */ jsxs11(Button, { type: "button", onClick: onStart, disabled, children: [
|
|
3177
|
-
/* @__PURE__ */ jsx21(Mic, { className: "h-4 w-4" }),
|
|
3178
|
-
labels?.voiceStart || "Start recording"
|
|
3179
|
-
] }),
|
|
3180
|
-
isCapturing && /* @__PURE__ */ jsxs11(Fragment4, { children: [
|
|
3181
|
-
/* @__PURE__ */ jsxs11(Button, { type: "button", onClick: onStop, disabled, children: [
|
|
3182
|
-
/* @__PURE__ */ jsx21(Square, { className: "h-4 w-4" }),
|
|
3183
|
-
labels?.voiceStop || "Stop recording"
|
|
3304
|
+
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
|
+
] }) }) : /* @__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-start justify-between gap-2", children: [
|
|
3307
|
+
/* @__PURE__ */ jsxs11("div", { className: "min-w-0", children: [
|
|
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) })
|
|
3184
3310
|
] }),
|
|
3185
|
-
/* @__PURE__ */
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3311
|
+
/* @__PURE__ */ jsx21(
|
|
3312
|
+
Button,
|
|
3313
|
+
{
|
|
3314
|
+
type: "button",
|
|
3315
|
+
variant: "ghost",
|
|
3316
|
+
size: "icon",
|
|
3317
|
+
className: "h-8 w-8 shrink-0 text-muted-foreground hover:text-destructive",
|
|
3318
|
+
onClick: onDiscard,
|
|
3319
|
+
disabled,
|
|
3320
|
+
"aria-label": labels?.voiceDiscard || "Delete recording",
|
|
3321
|
+
title: labels?.voiceDiscard || "Delete recording",
|
|
3322
|
+
children: /* @__PURE__ */ jsx21(Trash23, { className: "h-4 w-4" })
|
|
3323
|
+
}
|
|
3324
|
+
)
|
|
3325
|
+
] }),
|
|
3326
|
+
attachment && /* @__PURE__ */ jsx21("div", { className: "mt-3 rounded-lg bg-background p-2", children: /* @__PURE__ */ jsx21("audio", { controls: true, preload: "metadata", className: "w-full", children: /* @__PURE__ */ jsx21("source", { src: attachment.dataUrl, type: attachment.mimeType }) }) }),
|
|
3327
|
+
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 }),
|
|
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) })
|
|
3189
3331
|
] }),
|
|
3190
|
-
|
|
3191
|
-
/* @__PURE__ */ jsxs11(Button, { type: "button", variant: "
|
|
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: [
|
|
3192
3334
|
/* @__PURE__ */ jsx21(X2, { className: "h-4 w-4" }),
|
|
3193
3335
|
labels?.voiceCancel || "Cancel"
|
|
3194
3336
|
] }),
|
|
3195
|
-
/* @__PURE__ */
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3337
|
+
!isAutoSendActive && /* @__PURE__ */ jsx21(
|
|
3338
|
+
Button,
|
|
3339
|
+
{
|
|
3340
|
+
type: "button",
|
|
3341
|
+
variant: "outline",
|
|
3342
|
+
size: "icon",
|
|
3343
|
+
onClick: onRecordAgain,
|
|
3344
|
+
disabled,
|
|
3345
|
+
"aria-label": labels?.voiceRecordAgain || "Record again",
|
|
3346
|
+
title: labels?.voiceRecordAgain || "Record again",
|
|
3347
|
+
children: /* @__PURE__ */ jsx21(Mic, { className: "h-4 w-4" })
|
|
3348
|
+
}
|
|
3349
|
+
),
|
|
3350
|
+
/* @__PURE__ */ jsxs11(Button, { type: "button", size: "sm", onClick: onSendNow, disabled, children: [
|
|
3200
3351
|
/* @__PURE__ */ jsx21(Send, { className: "h-4 w-4" }),
|
|
3201
3352
|
labels?.voiceSendNow || "Send now"
|
|
3202
3353
|
] })
|
|
3203
|
-
] }),
|
|
3204
|
-
state === "error" && /* @__PURE__ */ jsxs11(Fragment4, { children: [
|
|
3205
|
-
/* @__PURE__ */ jsxs11(Button, { type: "button", variant: "outline", onClick: onCancel, disabled, children: [
|
|
3206
|
-
/* @__PURE__ */ jsx21(X2, { className: "h-4 w-4" }),
|
|
3207
|
-
labels?.voiceCancel || "Cancel"
|
|
3208
|
-
] }),
|
|
3209
|
-
/* @__PURE__ */ jsxs11(Button, { type: "button", onClick: onRecordAgain, disabled, children: [
|
|
3210
|
-
/* @__PURE__ */ jsx21(RotateCcw2, { className: "h-4 w-4" }),
|
|
3211
|
-
labels?.voiceRecordAgain || "Record again"
|
|
3212
|
-
] })
|
|
3213
3354
|
] })
|
|
3214
|
-
] })
|
|
3355
|
+
] }),
|
|
3356
|
+
errorMessage && /* @__PURE__ */ jsx21("div", { className: "mt-3 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2 text-sm text-destructive", children: errorMessage })
|
|
3215
3357
|
] });
|
|
3216
3358
|
};
|
|
3217
3359
|
|
|
@@ -3229,7 +3371,7 @@ import {
|
|
|
3229
3371
|
Pause,
|
|
3230
3372
|
Loader2 as Loader22
|
|
3231
3373
|
} from "lucide-react";
|
|
3232
|
-
import { Fragment as
|
|
3374
|
+
import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3233
3375
|
var FileUploadItem = memo2(function FileUploadItem2({ file, progress, onCancel }) {
|
|
3234
3376
|
const guessTypeFromName = (name) => {
|
|
3235
3377
|
const ext = (name || "").split(".").pop()?.toLowerCase();
|
|
@@ -3330,7 +3472,7 @@ var AttachmentPreview = memo2(function AttachmentPreview2({ attachment, onRemove
|
|
|
3330
3472
|
"img",
|
|
3331
3473
|
{
|
|
3332
3474
|
src: attachment.dataUrl,
|
|
3333
|
-
alt: attachment.fileName || "
|
|
3475
|
+
alt: attachment.fileName || "Attachment",
|
|
3334
3476
|
className: "w-full h-20 object-cover rounded"
|
|
3335
3477
|
}
|
|
3336
3478
|
),
|
|
@@ -3379,7 +3521,7 @@ var AttachmentPreview = memo2(function AttachmentPreview2({ attachment, onRemove
|
|
|
3379
3521
|
}
|
|
3380
3522
|
),
|
|
3381
3523
|
/* @__PURE__ */ jsxs12("div", { className: "flex-1", children: [
|
|
3382
|
-
/* @__PURE__ */ jsx22("p", { className: "text-xs font-medium", children: attachment.fileName || "
|
|
3524
|
+
/* @__PURE__ */ jsx22("p", { className: "text-xs font-medium", children: attachment.fileName || "Audio" }),
|
|
3383
3525
|
/* @__PURE__ */ jsx22("p", { className: "text-xs text-muted-foreground", children: formatDuration2(attachment.durationMs) })
|
|
3384
3526
|
] }),
|
|
3385
3527
|
/* @__PURE__ */ jsx22(
|
|
@@ -3431,7 +3573,7 @@ var AudioRecorder = memo2(function AudioRecorder2({ isRecording, onStartRecordin
|
|
|
3431
3573
|
return /* @__PURE__ */ jsx22(Card, { className: "border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950", children: /* @__PURE__ */ jsx22(CardContent, { className: "p-3", children: /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-3", children: [
|
|
3432
3574
|
/* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
|
|
3433
3575
|
/* @__PURE__ */ jsx22("div", { className: "h-3 w-3 bg-red-500 rounded-full animate-pulse" }),
|
|
3434
|
-
/* @__PURE__ */ jsx22("span", { className: "text-sm font-medium text-red-700 dark:text-red-300", children: "
|
|
3576
|
+
/* @__PURE__ */ jsx22("span", { className: "text-sm font-medium text-red-700 dark:text-red-300", children: config?.labels?.voiceListening || "Recording" })
|
|
3435
3577
|
] }),
|
|
3436
3578
|
/* @__PURE__ */ jsx22(Badge, { variant: "outline", className: "text-xs", children: formatTime(recordingDuration) }),
|
|
3437
3579
|
/* @__PURE__ */ jsxs12("div", { className: "flex gap-1 ml-auto", children: [
|
|
@@ -3443,7 +3585,7 @@ var AudioRecorder = memo2(function AudioRecorder2({ isRecording, onStartRecordin
|
|
|
3443
3585
|
onClick: onCancel,
|
|
3444
3586
|
children: [
|
|
3445
3587
|
/* @__PURE__ */ jsx22(X3, { className: "h-3 w-3 mr-1" }),
|
|
3446
|
-
"
|
|
3588
|
+
config?.labels?.cancel || "Cancel"
|
|
3447
3589
|
]
|
|
3448
3590
|
}
|
|
3449
3591
|
),
|
|
@@ -3455,7 +3597,7 @@ var AudioRecorder = memo2(function AudioRecorder2({ isRecording, onStartRecordin
|
|
|
3455
3597
|
onClick: onStopRecording,
|
|
3456
3598
|
children: [
|
|
3457
3599
|
/* @__PURE__ */ jsx22(Square2, { className: "h-3 w-3 mr-1" }),
|
|
3458
|
-
"
|
|
3600
|
+
config?.labels?.voiceStop || "Stop"
|
|
3459
3601
|
]
|
|
3460
3602
|
}
|
|
3461
3603
|
)
|
|
@@ -3472,13 +3614,14 @@ var resolveVoiceErrorMessage = (error, config) => {
|
|
|
3472
3614
|
return config?.labels?.voiceCaptureError || "Unable to capture audio.";
|
|
3473
3615
|
};
|
|
3474
3616
|
var clearVoiceTranscript = () => ({});
|
|
3617
|
+
var resolveVoiceSegmentDuration = (segment) => segment.attachment.durationMs ?? 0;
|
|
3475
3618
|
var ChatInput = memo2(function ChatInput2({
|
|
3476
3619
|
value,
|
|
3477
3620
|
onChange,
|
|
3478
3621
|
onSubmit,
|
|
3479
3622
|
attachments,
|
|
3480
3623
|
onAttachmentsChange,
|
|
3481
|
-
placeholder = "
|
|
3624
|
+
placeholder = "Type your message...",
|
|
3482
3625
|
disabled = false,
|
|
3483
3626
|
isGenerating = false,
|
|
3484
3627
|
onStopGeneration,
|
|
@@ -3491,17 +3634,27 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3491
3634
|
className = "",
|
|
3492
3635
|
config
|
|
3493
3636
|
}) {
|
|
3637
|
+
const voiceComposeEnabled = config?.voiceCompose?.enabled === true;
|
|
3638
|
+
const voiceDefaultMode = config?.voiceCompose?.defaultMode ?? "text";
|
|
3639
|
+
const voiceAutoSendDelayMs = config?.voiceCompose?.autoSendDelayMs ?? 5e3;
|
|
3640
|
+
const voicePersistComposer = config?.voiceCompose?.persistComposer ?? true;
|
|
3641
|
+
const voiceShowTranscriptPreview = config?.voiceCompose?.showTranscriptPreview ?? true;
|
|
3642
|
+
const voiceTranscriptMode = config?.voiceCompose?.transcriptMode ?? "final-only";
|
|
3643
|
+
const voiceMaxRecordingMs = config?.voiceCompose?.maxRecordingMs;
|
|
3494
3644
|
const [isRecording, setIsRecording] = useState6(false);
|
|
3495
3645
|
const { setContext } = useChatUserContext();
|
|
3496
3646
|
const [recordingDuration, setRecordingDuration] = useState6(0);
|
|
3497
3647
|
const [uploadProgress, setUploadProgress] = useState6(/* @__PURE__ */ new Map());
|
|
3498
|
-
const [isVoiceComposerOpen, setIsVoiceComposerOpen] = useState6(
|
|
3648
|
+
const [isVoiceComposerOpen, setIsVoiceComposerOpen] = useState6(
|
|
3649
|
+
() => voiceComposeEnabled && voiceDefaultMode === "voice"
|
|
3650
|
+
);
|
|
3499
3651
|
const [voiceState, setVoiceState] = useState6("idle");
|
|
3500
3652
|
const [voiceDraft, setVoiceDraft] = useState6(null);
|
|
3501
3653
|
const [voiceTranscript, setVoiceTranscript] = useState6(clearVoiceTranscript);
|
|
3502
3654
|
const [voiceDurationMs, setVoiceDurationMs] = useState6(0);
|
|
3503
3655
|
const [voiceAudioLevel, setVoiceAudioLevel] = useState6(0);
|
|
3504
3656
|
const [voiceCountdownMs, setVoiceCountdownMs] = useState6(0);
|
|
3657
|
+
const [isVoiceAutoSendActive, setIsVoiceAutoSendActive] = useState6(false);
|
|
3505
3658
|
const [voiceError, setVoiceError] = useState6(null);
|
|
3506
3659
|
const textareaRef = useRef5(null);
|
|
3507
3660
|
const fileInputRef = useRef5(null);
|
|
@@ -3510,12 +3663,9 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3510
3663
|
const recordingInterval = useRef5(null);
|
|
3511
3664
|
const mediaStreamRef = useRef5(null);
|
|
3512
3665
|
const voiceProviderRef = useRef5(null);
|
|
3513
|
-
const
|
|
3514
|
-
const
|
|
3515
|
-
const
|
|
3516
|
-
const voiceShowTranscriptPreview = config?.voiceCompose?.showTranscriptPreview ?? true;
|
|
3517
|
-
const voiceTranscriptMode = config?.voiceCompose?.transcriptMode ?? "final-only";
|
|
3518
|
-
const voiceMaxRecordingMs = config?.voiceCompose?.maxRecordingMs;
|
|
3666
|
+
const voiceDraftRef = useRef5(null);
|
|
3667
|
+
const voiceAppendBaseRef = useRef5(null);
|
|
3668
|
+
const voiceAppendBaseDurationRef = useRef5(0);
|
|
3519
3669
|
useEffect9(() => {
|
|
3520
3670
|
return () => {
|
|
3521
3671
|
if (mediaStreamRef.current) {
|
|
@@ -3530,6 +3680,9 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3530
3680
|
}
|
|
3531
3681
|
};
|
|
3532
3682
|
}, []);
|
|
3683
|
+
useEffect9(() => {
|
|
3684
|
+
voiceDraftRef.current = voiceDraft;
|
|
3685
|
+
}, [voiceDraft]);
|
|
3533
3686
|
const handleSubmit = (e) => {
|
|
3534
3687
|
e.preventDefault();
|
|
3535
3688
|
if (!value.trim() && attachments.length === 0 || disabled || isGenerating) return;
|
|
@@ -3545,7 +3698,7 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3545
3698
|
};
|
|
3546
3699
|
const processFile = async (file) => {
|
|
3547
3700
|
if (file.size > maxFileSize) {
|
|
3548
|
-
alert(`
|
|
3701
|
+
alert(`File too large. Max allowed: ${Math.round(maxFileSize / 1024 / 1024)}MB`);
|
|
3549
3702
|
return null;
|
|
3550
3703
|
}
|
|
3551
3704
|
const fileId = `${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
@@ -3604,7 +3757,7 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3604
3757
|
newMap.delete(fileId);
|
|
3605
3758
|
return newMap;
|
|
3606
3759
|
});
|
|
3607
|
-
alert("
|
|
3760
|
+
alert("Failed to process file");
|
|
3608
3761
|
return null;
|
|
3609
3762
|
}
|
|
3610
3763
|
};
|
|
@@ -3679,7 +3832,7 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3679
3832
|
}, 1e3);
|
|
3680
3833
|
} catch (error) {
|
|
3681
3834
|
console.error("Error starting recording:", error);
|
|
3682
|
-
alert(
|
|
3835
|
+
alert(config?.labels?.voicePermissionDenied || "Microphone access was denied.");
|
|
3683
3836
|
}
|
|
3684
3837
|
};
|
|
3685
3838
|
const stopRecording = () => {
|
|
@@ -3707,10 +3860,14 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3707
3860
|
const resetVoiceComposerState = useCallback3((nextState = "idle") => {
|
|
3708
3861
|
setVoiceState(nextState);
|
|
3709
3862
|
setVoiceDraft(null);
|
|
3863
|
+
voiceDraftRef.current = null;
|
|
3864
|
+
voiceAppendBaseRef.current = null;
|
|
3865
|
+
voiceAppendBaseDurationRef.current = 0;
|
|
3710
3866
|
setVoiceTranscript(clearVoiceTranscript());
|
|
3711
3867
|
setVoiceDurationMs(0);
|
|
3712
3868
|
setVoiceAudioLevel(0);
|
|
3713
3869
|
setVoiceCountdownMs(0);
|
|
3870
|
+
setIsVoiceAutoSendActive(false);
|
|
3714
3871
|
setVoiceError(null);
|
|
3715
3872
|
}, []);
|
|
3716
3873
|
const ensureVoiceProvider = useCallback3(async () => {
|
|
@@ -3721,21 +3878,76 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3721
3878
|
const provider = await createProvider({
|
|
3722
3879
|
onStateChange: setVoiceState,
|
|
3723
3880
|
onAudioLevelChange: setVoiceAudioLevel,
|
|
3724
|
-
onDurationChange:
|
|
3725
|
-
|
|
3881
|
+
onDurationChange: (durationMs) => {
|
|
3882
|
+
setVoiceDurationMs(voiceAppendBaseDurationRef.current + durationMs);
|
|
3883
|
+
},
|
|
3884
|
+
onTranscriptChange: (transcript) => {
|
|
3885
|
+
const baseTranscript = voiceAppendBaseRef.current?.transcript;
|
|
3886
|
+
setVoiceTranscript(
|
|
3887
|
+
baseTranscript ? mergeVoiceTranscripts(baseTranscript, transcript) : transcript
|
|
3888
|
+
);
|
|
3889
|
+
},
|
|
3726
3890
|
onSegmentReady: (segment) => {
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3891
|
+
void (async () => {
|
|
3892
|
+
const previousSegment = voiceAppendBaseRef.current;
|
|
3893
|
+
try {
|
|
3894
|
+
const nextSegment = previousSegment ? await appendVoiceSegments(previousSegment, segment) : segment;
|
|
3895
|
+
voiceAppendBaseRef.current = null;
|
|
3896
|
+
voiceAppendBaseDurationRef.current = 0;
|
|
3897
|
+
voiceDraftRef.current = nextSegment;
|
|
3898
|
+
setVoiceDraft(nextSegment);
|
|
3899
|
+
setVoiceTranscript(nextSegment.transcript ?? clearVoiceTranscript());
|
|
3900
|
+
setVoiceDurationMs(resolveVoiceSegmentDuration(nextSegment));
|
|
3901
|
+
setVoiceAudioLevel(0);
|
|
3902
|
+
setVoiceCountdownMs(voiceAutoSendDelayMs);
|
|
3903
|
+
setIsVoiceAutoSendActive(voiceAutoSendDelayMs > 0);
|
|
3904
|
+
setVoiceError(null);
|
|
3905
|
+
setVoiceState("review");
|
|
3906
|
+
} catch (error) {
|
|
3907
|
+
const resolvedError = resolveVoiceErrorMessage(error, config);
|
|
3908
|
+
voiceAppendBaseRef.current = null;
|
|
3909
|
+
voiceAppendBaseDurationRef.current = 0;
|
|
3910
|
+
setVoiceAudioLevel(0);
|
|
3911
|
+
setVoiceCountdownMs(0);
|
|
3912
|
+
setIsVoiceAutoSendActive(false);
|
|
3913
|
+
if (previousSegment) {
|
|
3914
|
+
voiceDraftRef.current = previousSegment;
|
|
3915
|
+
setVoiceDraft(previousSegment);
|
|
3916
|
+
setVoiceTranscript(previousSegment.transcript ?? clearVoiceTranscript());
|
|
3917
|
+
setVoiceDurationMs(resolveVoiceSegmentDuration(previousSegment));
|
|
3918
|
+
setVoiceError(resolvedError);
|
|
3919
|
+
setVoiceState("review");
|
|
3920
|
+
return;
|
|
3921
|
+
}
|
|
3922
|
+
voiceDraftRef.current = null;
|
|
3923
|
+
setVoiceDraft(null);
|
|
3924
|
+
setVoiceTranscript(clearVoiceTranscript());
|
|
3925
|
+
setVoiceDurationMs(0);
|
|
3926
|
+
setVoiceError(resolvedError);
|
|
3927
|
+
setVoiceState("error");
|
|
3928
|
+
}
|
|
3929
|
+
})();
|
|
3734
3930
|
},
|
|
3735
3931
|
onError: (error) => {
|
|
3932
|
+
const previousSegment = voiceAppendBaseRef.current;
|
|
3933
|
+
voiceAppendBaseRef.current = null;
|
|
3934
|
+
voiceAppendBaseDurationRef.current = 0;
|
|
3736
3935
|
setVoiceError(resolveVoiceErrorMessage(error, config));
|
|
3737
3936
|
setVoiceAudioLevel(0);
|
|
3738
3937
|
setVoiceCountdownMs(0);
|
|
3938
|
+
setIsVoiceAutoSendActive(false);
|
|
3939
|
+
if (previousSegment) {
|
|
3940
|
+
voiceDraftRef.current = previousSegment;
|
|
3941
|
+
setVoiceDraft(previousSegment);
|
|
3942
|
+
setVoiceTranscript(previousSegment.transcript ?? clearVoiceTranscript());
|
|
3943
|
+
setVoiceDurationMs(resolveVoiceSegmentDuration(previousSegment));
|
|
3944
|
+
setVoiceState("review");
|
|
3945
|
+
return;
|
|
3946
|
+
}
|
|
3947
|
+
voiceDraftRef.current = null;
|
|
3948
|
+
setVoiceDraft(null);
|
|
3949
|
+
setVoiceTranscript(clearVoiceTranscript());
|
|
3950
|
+
setVoiceDurationMs(0);
|
|
3739
3951
|
setVoiceState("error");
|
|
3740
3952
|
}
|
|
3741
3953
|
}, {
|
|
@@ -3745,34 +3957,67 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3745
3957
|
return provider;
|
|
3746
3958
|
}, [config, voiceAutoSendDelayMs, voiceMaxRecordingMs]);
|
|
3747
3959
|
const closeVoiceComposer = useCallback3(async () => {
|
|
3960
|
+
voiceAppendBaseRef.current = null;
|
|
3961
|
+
voiceAppendBaseDurationRef.current = 0;
|
|
3748
3962
|
setIsVoiceComposerOpen(false);
|
|
3749
3963
|
setVoiceError(null);
|
|
3750
3964
|
setVoiceCountdownMs(0);
|
|
3751
3965
|
setVoiceAudioLevel(0);
|
|
3752
3966
|
setVoiceTranscript(clearVoiceTranscript());
|
|
3753
3967
|
setVoiceDraft(null);
|
|
3968
|
+
voiceDraftRef.current = null;
|
|
3754
3969
|
setVoiceDurationMs(0);
|
|
3755
3970
|
setVoiceState("idle");
|
|
3756
3971
|
if (voiceProviderRef.current) {
|
|
3757
3972
|
await voiceProviderRef.current.cancel();
|
|
3758
3973
|
}
|
|
3759
3974
|
}, []);
|
|
3760
|
-
const startVoiceCapture = useCallback3(async () => {
|
|
3975
|
+
const startVoiceCapture = useCallback3(async (appendToDraft = false) => {
|
|
3761
3976
|
if (disabled || isGenerating) {
|
|
3762
3977
|
return;
|
|
3763
3978
|
}
|
|
3979
|
+
const previousDraft = appendToDraft ? voiceDraftRef.current : null;
|
|
3980
|
+
const previousDurationMs = previousDraft ? resolveVoiceSegmentDuration(previousDraft) : 0;
|
|
3764
3981
|
setIsVoiceComposerOpen(true);
|
|
3765
3982
|
setVoiceError(null);
|
|
3766
|
-
setVoiceDraft(null);
|
|
3767
3983
|
setVoiceCountdownMs(0);
|
|
3768
|
-
setVoiceTranscript(clearVoiceTranscript());
|
|
3769
3984
|
setVoiceAudioLevel(0);
|
|
3770
|
-
|
|
3985
|
+
setIsVoiceAutoSendActive(false);
|
|
3986
|
+
voiceAppendBaseRef.current = previousDraft;
|
|
3987
|
+
voiceAppendBaseDurationRef.current = previousDurationMs;
|
|
3988
|
+
if (!previousDraft) {
|
|
3989
|
+
setVoiceDraft(null);
|
|
3990
|
+
voiceDraftRef.current = null;
|
|
3991
|
+
setVoiceTranscript(clearVoiceTranscript());
|
|
3992
|
+
setVoiceDurationMs(0);
|
|
3993
|
+
} else {
|
|
3994
|
+
setVoiceTranscript(previousDraft.transcript ?? clearVoiceTranscript());
|
|
3995
|
+
setVoiceDurationMs(previousDurationMs);
|
|
3996
|
+
}
|
|
3771
3997
|
try {
|
|
3772
3998
|
const provider = await ensureVoiceProvider();
|
|
3773
3999
|
await provider.start();
|
|
3774
4000
|
} catch (error) {
|
|
3775
|
-
|
|
4001
|
+
const resolvedError = resolveVoiceErrorMessage(error, config);
|
|
4002
|
+
voiceAppendBaseRef.current = null;
|
|
4003
|
+
voiceAppendBaseDurationRef.current = 0;
|
|
4004
|
+
setVoiceAudioLevel(0);
|
|
4005
|
+
setVoiceCountdownMs(0);
|
|
4006
|
+
setIsVoiceAutoSendActive(false);
|
|
4007
|
+
if (previousDraft) {
|
|
4008
|
+
voiceDraftRef.current = previousDraft;
|
|
4009
|
+
setVoiceDraft(previousDraft);
|
|
4010
|
+
setVoiceTranscript(previousDraft.transcript ?? clearVoiceTranscript());
|
|
4011
|
+
setVoiceDurationMs(previousDurationMs);
|
|
4012
|
+
setVoiceError(resolvedError);
|
|
4013
|
+
setVoiceState("review");
|
|
4014
|
+
return;
|
|
4015
|
+
}
|
|
4016
|
+
voiceDraftRef.current = null;
|
|
4017
|
+
setVoiceDraft(null);
|
|
4018
|
+
setVoiceTranscript(clearVoiceTranscript());
|
|
4019
|
+
setVoiceDurationMs(0);
|
|
4020
|
+
setVoiceError(resolvedError);
|
|
3776
4021
|
setVoiceState("error");
|
|
3777
4022
|
}
|
|
3778
4023
|
}, [disabled, isGenerating, ensureVoiceProvider, config]);
|
|
@@ -3786,6 +4031,8 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3786
4031
|
}
|
|
3787
4032
|
}, [config]);
|
|
3788
4033
|
const cancelVoiceCapture = useCallback3(async () => {
|
|
4034
|
+
voiceAppendBaseRef.current = null;
|
|
4035
|
+
voiceAppendBaseDurationRef.current = 0;
|
|
3789
4036
|
if (voiceProviderRef.current) {
|
|
3790
4037
|
await voiceProviderRef.current.cancel();
|
|
3791
4038
|
}
|
|
@@ -3805,6 +4052,7 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3805
4052
|
}
|
|
3806
4053
|
setVoiceState("sending");
|
|
3807
4054
|
setVoiceCountdownMs(0);
|
|
4055
|
+
setIsVoiceAutoSendActive(false);
|
|
3808
4056
|
onSubmit("", [...attachments, voiceDraft.attachment]);
|
|
3809
4057
|
onChange("");
|
|
3810
4058
|
onAttachmentsChange([]);
|
|
@@ -3819,12 +4067,12 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3819
4067
|
onAttachmentsChange,
|
|
3820
4068
|
finalizeVoiceComposerAfterSend
|
|
3821
4069
|
]);
|
|
3822
|
-
const
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
}, [
|
|
4070
|
+
const cancelVoiceAutoSend = useCallback3(() => {
|
|
4071
|
+
setVoiceCountdownMs(0);
|
|
4072
|
+
setIsVoiceAutoSendActive(false);
|
|
4073
|
+
}, []);
|
|
3826
4074
|
useEffect9(() => {
|
|
3827
|
-
if (voiceState !== "review" || !voiceDraft || voiceAutoSendDelayMs <= 0) {
|
|
4075
|
+
if (voiceState !== "review" || !voiceDraft || voiceAutoSendDelayMs <= 0 || !isVoiceAutoSendActive) {
|
|
3828
4076
|
return;
|
|
3829
4077
|
}
|
|
3830
4078
|
const startedAt = Date.now();
|
|
@@ -3838,7 +4086,7 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3838
4086
|
}
|
|
3839
4087
|
}, 100);
|
|
3840
4088
|
return () => clearInterval(timer);
|
|
3841
|
-
}, [voiceState, voiceDraft, voiceAutoSendDelayMs, sendVoiceDraft]);
|
|
4089
|
+
}, [voiceState, voiceDraft, voiceAutoSendDelayMs, isVoiceAutoSendActive, sendVoiceDraft]);
|
|
3842
4090
|
const removeAttachment = (index) => {
|
|
3843
4091
|
const newAttachments = attachments.filter((_, i) => i !== index);
|
|
3844
4092
|
onAttachmentsChange(newAttachments);
|
|
@@ -3887,10 +4135,12 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3887
4135
|
transcript: voiceTranscript,
|
|
3888
4136
|
transcriptMode: voiceTranscriptMode,
|
|
3889
4137
|
showTranscriptPreview: voiceShowTranscriptPreview,
|
|
4138
|
+
attachment: voiceDraft?.attachment ?? null,
|
|
3890
4139
|
durationMs: voiceDurationMs,
|
|
3891
4140
|
audioLevel: voiceAudioLevel,
|
|
3892
4141
|
countdownMs: voiceCountdownMs,
|
|
3893
4142
|
autoSendDelayMs: voiceAutoSendDelayMs,
|
|
4143
|
+
isAutoSendActive: isVoiceAutoSendActive,
|
|
3894
4144
|
errorMessage: voiceError,
|
|
3895
4145
|
disabled: disabled || isGenerating,
|
|
3896
4146
|
labels: config?.labels,
|
|
@@ -3900,13 +4150,16 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3900
4150
|
onStop: () => {
|
|
3901
4151
|
void stopVoiceCapture();
|
|
3902
4152
|
},
|
|
3903
|
-
|
|
4153
|
+
onCancelAutoSend: () => {
|
|
4154
|
+
cancelVoiceAutoSend();
|
|
4155
|
+
},
|
|
4156
|
+
onDiscard: () => {
|
|
3904
4157
|
void cancelVoiceCapture();
|
|
3905
4158
|
},
|
|
3906
|
-
onSendNow: sendVoiceDraft,
|
|
3907
4159
|
onRecordAgain: () => {
|
|
3908
|
-
void
|
|
4160
|
+
void startVoiceCapture(true);
|
|
3909
4161
|
},
|
|
4162
|
+
onSendNow: sendVoiceDraft,
|
|
3910
4163
|
onExit: () => {
|
|
3911
4164
|
void closeVoiceComposer();
|
|
3912
4165
|
}
|
|
@@ -3918,7 +4171,7 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
3918
4171
|
onDrop: handleDrop,
|
|
3919
4172
|
onDragOver: handleDragOver,
|
|
3920
4173
|
children: [
|
|
3921
|
-
enableFileUpload && canAddMoreAttachments && /* @__PURE__ */ jsxs12(
|
|
4174
|
+
enableFileUpload && canAddMoreAttachments && /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
3922
4175
|
/* @__PURE__ */ jsx22(
|
|
3923
4176
|
"input",
|
|
3924
4177
|
{
|
|
@@ -4021,14 +4274,14 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
4021
4274
|
) }),
|
|
4022
4275
|
/* @__PURE__ */ jsxs12("div", { className: "text-[10px] text-muted-foreground text-center", children: [
|
|
4023
4276
|
window.innerWidth > 768 ? config?.labels?.inputHelpText : "",
|
|
4024
|
-
attachments.length > 0 && /* @__PURE__ */ jsxs12(
|
|
4277
|
+
attachments.length > 0 && /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
4025
4278
|
" \u2022 ",
|
|
4026
4279
|
attachments.length,
|
|
4027
4280
|
"/",
|
|
4028
4281
|
maxAttachments,
|
|
4029
4282
|
" anexos"
|
|
4030
4283
|
] }),
|
|
4031
|
-
config?.labels?.footerLabel && /* @__PURE__ */ jsxs12(
|
|
4284
|
+
config?.labels?.footerLabel && /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
4032
4285
|
" \u2022 ",
|
|
4033
4286
|
config.labels.footerLabel
|
|
4034
4287
|
] })
|
|
@@ -4118,7 +4371,7 @@ import {
|
|
|
4118
4371
|
FileText as FileText2,
|
|
4119
4372
|
Brain as Brain2,
|
|
4120
4373
|
Plus as Plus3,
|
|
4121
|
-
Trash2 as
|
|
4374
|
+
Trash2 as Trash24,
|
|
4122
4375
|
Target,
|
|
4123
4376
|
Lightbulb,
|
|
4124
4377
|
Info,
|
|
@@ -4128,7 +4381,7 @@ import {
|
|
|
4128
4381
|
Check as Check3,
|
|
4129
4382
|
X as X4
|
|
4130
4383
|
} from "lucide-react";
|
|
4131
|
-
import { Fragment as
|
|
4384
|
+
import { Fragment as Fragment5, jsx as jsx24, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
4132
4385
|
var getInitials2 = (name, email) => {
|
|
4133
4386
|
if (name) {
|
|
4134
4387
|
return name.split(" ").map((n) => n[0]).slice(0, 2).join("").toUpperCase();
|
|
@@ -4321,7 +4574,7 @@ var UserProfile = ({
|
|
|
4321
4574
|
] })
|
|
4322
4575
|
] })
|
|
4323
4576
|
] }),
|
|
4324
|
-
normalizedFields.length > 0 && /* @__PURE__ */ jsxs14(
|
|
4577
|
+
normalizedFields.length > 0 && /* @__PURE__ */ jsxs14(Fragment5, { children: [
|
|
4325
4578
|
/* @__PURE__ */ jsx24(Separator, {}),
|
|
4326
4579
|
/* @__PURE__ */ jsxs14("div", { className: "space-y-3", children: [
|
|
4327
4580
|
/* @__PURE__ */ jsx24("h3", { className: "text-sm font-medium text-muted-foreground uppercase tracking-wider", children: labels.customFields }),
|
|
@@ -4465,7 +4718,7 @@ var UserProfile = ({
|
|
|
4465
4718
|
size: "icon",
|
|
4466
4719
|
className: "h-7 w-7",
|
|
4467
4720
|
onClick: () => onDeleteMemory(memory.id),
|
|
4468
|
-
children: /* @__PURE__ */ jsx24(
|
|
4721
|
+
children: /* @__PURE__ */ jsx24(Trash24, { className: "h-3.5 w-3.5 text-destructive" })
|
|
4469
4722
|
}
|
|
4470
4723
|
)
|
|
4471
4724
|
] })
|
|
@@ -5051,7 +5304,7 @@ import {
|
|
|
5051
5304
|
MessageSquare as MessageSquare2,
|
|
5052
5305
|
MoreVertical as MoreVertical2,
|
|
5053
5306
|
Edit2 as Edit22,
|
|
5054
|
-
Trash2 as
|
|
5307
|
+
Trash2 as Trash25,
|
|
5055
5308
|
Archive as Archive2,
|
|
5056
5309
|
Search as Search2,
|
|
5057
5310
|
Filter as Filter2,
|
|
@@ -5060,7 +5313,7 @@ import {
|
|
|
5060
5313
|
X as X5,
|
|
5061
5314
|
Check as Check4
|
|
5062
5315
|
} from "lucide-react";
|
|
5063
|
-
import { Fragment as
|
|
5316
|
+
import { Fragment as Fragment6, jsx as jsx26, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
5064
5317
|
var ThreadItem = ({ thread, isActive, config, onSelect, onRename, onDelete, onArchive }) => {
|
|
5065
5318
|
const [isEditing, setIsEditing] = useState9(false);
|
|
5066
5319
|
const [editTitle, setEditTitle] = useState9(thread.title);
|
|
@@ -5105,7 +5358,7 @@ var ThreadItem = ({ thread, isActive, config, onSelect, onRename, onDelete, onAr
|
|
|
5105
5358
|
),
|
|
5106
5359
|
/* @__PURE__ */ jsx26(Button, { size: "sm", variant: "ghost", onClick: handleSaveEdit, children: /* @__PURE__ */ jsx26(Check4, { className: "h-3 w-3" }) }),
|
|
5107
5360
|
/* @__PURE__ */ jsx26(Button, { size: "sm", variant: "ghost", onClick: handleCancelEdit, children: /* @__PURE__ */ jsx26(X5, { className: "h-3 w-3" }) })
|
|
5108
|
-
] }) : /* @__PURE__ */ jsxs16(
|
|
5361
|
+
] }) : /* @__PURE__ */ jsxs16(Fragment6, { children: [
|
|
5109
5362
|
/* @__PURE__ */ jsx26("h4", { className: "font-medium text-sm truncate mb-1", children: thread.title }),
|
|
5110
5363
|
/* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
5111
5364
|
/* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-1", children: [
|
|
@@ -5118,7 +5371,7 @@ var ThreadItem = ({ thread, isActive, config, onSelect, onRename, onDelete, onAr
|
|
|
5118
5371
|
/* @__PURE__ */ jsx26(Calendar2, { className: "h-3 w-3" }),
|
|
5119
5372
|
formatDate(thread.updatedAt, config?.labels)
|
|
5120
5373
|
] }),
|
|
5121
|
-
thread.isArchived && /* @__PURE__ */ jsxs16(
|
|
5374
|
+
thread.isArchived && /* @__PURE__ */ jsxs16(Fragment6, { children: [
|
|
5122
5375
|
/* @__PURE__ */ jsx26(Separator, { orientation: "vertical", className: "h-3" }),
|
|
5123
5376
|
/* @__PURE__ */ jsxs16(Badge, { variant: "secondary", className: "text-xs", children: [
|
|
5124
5377
|
/* @__PURE__ */ jsx26(Archive2, { className: "h-2 w-2 mr-1" }),
|
|
@@ -5140,7 +5393,7 @@ var ThreadItem = ({ thread, isActive, config, onSelect, onRename, onDelete, onAr
|
|
|
5140
5393
|
] }),
|
|
5141
5394
|
/* @__PURE__ */ jsx26(DropdownMenuSeparator, {}),
|
|
5142
5395
|
/* @__PURE__ */ jsxs16(DropdownMenuItem, { onClick: onDelete, className: "text-destructive", children: [
|
|
5143
|
-
/* @__PURE__ */ jsx26(
|
|
5396
|
+
/* @__PURE__ */ jsx26(Trash25, { className: "h-4 w-4 mr-2" }),
|
|
5144
5397
|
config?.labels?.deleteThread || "Delete"
|
|
5145
5398
|
] })
|
|
5146
5399
|
] })
|