@pmate/chat-sdk 0.1.11 → 0.1.13
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/lib/components/ChatInputMobile.js +174 -5
- package/lib/hooks/useVoiceInputController.d.ts +2 -0
- package/lib/hooks/useVoiceInputController.js +18 -2
- package/lib/types/voice.d.ts +1 -0
- package/package.json +1 -1
- package/dist/assets/ChatInput.stories-BABdkDrt.js +0 -1545
- package/dist/assets/Color-AVL7NMMY-D33umUzR.js +0 -1
- package/dist/assets/DocsRenderer-PQXLIZUC-BUYhEk48.js +0 -1243
- package/dist/assets/client-De2ACSn_.js +0 -9
- package/dist/assets/iframe-DVJDoCiO.js +0 -1071
- package/dist/assets/iframe-q28pAgAa.css +0 -1
- package/dist/assets/index-CkuJGuix.js +0 -1
- package/dist/assets/preload-helper-PPVm8Dsz.js +0 -1
- package/dist/assets/react-18-DfLUnvSL.js +0 -1
- package/dist/favicon-wrapper.svg +0 -46
- package/dist/favicon.svg +0 -1
- package/dist/iframe.html +0 -687
- package/dist/index.html +0 -148
- package/dist/index.json +0 -1
- package/dist/nunito-sans-bold-italic.woff2 +0 -0
- package/dist/nunito-sans-bold.woff2 +0 -0
- package/dist/nunito-sans-italic.woff2 +0 -0
- package/dist/nunito-sans-regular.woff2 +0 -0
- package/dist/project.json +0 -1
- package/dist/sb-addons/docs-1/manager-bundle.js +0 -151
- package/dist/sb-addons/docs-1/manager-bundle.js.LEGAL.txt +0 -0
- package/dist/sb-addons/storybook-core-server-presets-0/common-manager-bundle.js +0 -971
- package/dist/sb-addons/storybook-core-server-presets-0/common-manager-bundle.js.LEGAL.txt +0 -0
- package/dist/sb-common-assets/favicon-wrapper.svg +0 -46
- package/dist/sb-common-assets/favicon.svg +0 -1
- package/dist/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
- package/dist/sb-common-assets/nunito-sans-bold.woff2 +0 -0
- package/dist/sb-common-assets/nunito-sans-italic.woff2 +0 -0
- package/dist/sb-common-assets/nunito-sans-regular.woff2 +0 -0
- package/dist/sb-manager/globals-module-info.js +0 -799
- package/dist/sb-manager/globals-runtime.js +0 -69791
- package/dist/sb-manager/globals.js +0 -34
- package/dist/sb-manager/runtime.js +0 -13198
- package/dist/vite-inject-mocker-entry.js +0 -2
|
@@ -27,6 +27,7 @@ function MobileInner(props) {
|
|
|
27
27
|
const [isEmojiOpen, setIsEmojiOpen] = useState(false);
|
|
28
28
|
const [isMoreOpen, setIsMoreOpen] = useState(false);
|
|
29
29
|
const [isUploadingImage, setIsUploadingImage] = useState(false);
|
|
30
|
+
const [transcriptDraft, setTranscriptDraft] = useState("");
|
|
30
31
|
const cancelZoneRef = useRef(null);
|
|
31
32
|
const transcribeZoneRef = useRef(null);
|
|
32
33
|
const textComposerRef = useRef(null);
|
|
@@ -35,6 +36,7 @@ function MobileInner(props) {
|
|
|
35
36
|
const fileInputRef = useRef(null);
|
|
36
37
|
const voice = useVoiceInputController({
|
|
37
38
|
...props.voice,
|
|
39
|
+
confirmTranscription: props.voice?.confirmTranscription ?? true,
|
|
38
40
|
onVoiceTranscript: props.voice?.onVoiceTranscript,
|
|
39
41
|
onVoiceSend: async (blob) => {
|
|
40
42
|
await props.onSend?.({
|
|
@@ -50,6 +52,11 @@ function MobileInner(props) {
|
|
|
50
52
|
state.setText(props.value);
|
|
51
53
|
}
|
|
52
54
|
}, [props.value]);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (state.status === "transcript-ready") {
|
|
57
|
+
setTranscriptDraft(state.transcript);
|
|
58
|
+
}
|
|
59
|
+
}, [state.status, state.transcript]);
|
|
53
60
|
const statusText = state.status === "recording"
|
|
54
61
|
? "正在录音"
|
|
55
62
|
: state.status === "transcribing"
|
|
@@ -108,6 +115,10 @@ function MobileInner(props) {
|
|
|
108
115
|
});
|
|
109
116
|
};
|
|
110
117
|
const isRecordingOverlayVisible = state.status === "recording" || state.status === "transcribing";
|
|
118
|
+
const isTranscriptConfirmVisible = state.status === "transcript-ready" && Boolean(state.transcript);
|
|
119
|
+
const transcriptConfirmSurface = `color-mix(in srgb, ${tokens.color.surfaceInputFocus} 86%, ${tokens.color.brandPrimary})`;
|
|
120
|
+
const transcriptConfirmActionSurface = `color-mix(in srgb, ${tokens.color.surfaceInputFocus} 72%, transparent)`;
|
|
121
|
+
const transcriptConfirmActionBorder = `color-mix(in srgb, ${tokens.color.borderDefault} 72%, transparent)`;
|
|
111
122
|
const updateRecordingChoice = (clientX, clientY) => {
|
|
112
123
|
const isInside = (node) => {
|
|
113
124
|
if (!node) {
|
|
@@ -144,6 +155,44 @@ function MobileInner(props) {
|
|
|
144
155
|
textarea?.setSelectionRange(caret, caret);
|
|
145
156
|
});
|
|
146
157
|
};
|
|
158
|
+
const sendConfirmedTranscript = async () => {
|
|
159
|
+
const transcript = transcriptDraft.trim();
|
|
160
|
+
if (!transcript) {
|
|
161
|
+
voice.clearTranscriptPreview();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
state.setStatus("sending");
|
|
165
|
+
if (props.voice?.onVoiceTranscript) {
|
|
166
|
+
await props.voice.onVoiceTranscript(transcript, {
|
|
167
|
+
blob: voice.pendingTranscriptBlob ?? new Blob(),
|
|
168
|
+
action: "transcribe",
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
await props.onSend?.({
|
|
173
|
+
text: transcript,
|
|
174
|
+
attachments: state.attachments,
|
|
175
|
+
transcript,
|
|
176
|
+
mode: "voice",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
state.setText("");
|
|
180
|
+
props.onChange?.("");
|
|
181
|
+
voice.clearTranscriptPreview();
|
|
182
|
+
};
|
|
183
|
+
const sendConfirmedOriginalVoice = async () => {
|
|
184
|
+
if (!voice.pendingTranscriptBlob) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
state.setStatus("sending");
|
|
188
|
+
await props.onSend?.({
|
|
189
|
+
text: "",
|
|
190
|
+
attachments: state.attachments,
|
|
191
|
+
audio: voice.pendingTranscriptBlob,
|
|
192
|
+
mode: "voice",
|
|
193
|
+
});
|
|
194
|
+
voice.clearTranscriptPreview();
|
|
195
|
+
};
|
|
147
196
|
const attachmentActionStyle = {
|
|
148
197
|
display: "grid",
|
|
149
198
|
justifyItems: "center",
|
|
@@ -197,14 +246,16 @@ function MobileInner(props) {
|
|
|
197
246
|
boxShadow: "none",
|
|
198
247
|
overflow: "visible",
|
|
199
248
|
}, children: [isRecordingOverlayVisible ? (_jsxs("div", { style: {
|
|
200
|
-
position: "
|
|
201
|
-
left:
|
|
202
|
-
|
|
203
|
-
|
|
249
|
+
position: "fixed",
|
|
250
|
+
left: "50%",
|
|
251
|
+
bottom: "calc(72px + env(safe-area-inset-bottom, 0px))",
|
|
252
|
+
width: "min(420px, calc(100vw - 16px))",
|
|
253
|
+
transform: "translateX(-50%)",
|
|
204
254
|
height: 250,
|
|
205
255
|
pointerEvents: "none",
|
|
206
256
|
background: "linear-gradient(180deg, rgba(22, 16, 45, 0) 0%, rgba(22, 16, 45, 0.24) 34%, rgba(22, 16, 45, 0.6) 100%)",
|
|
207
257
|
borderRadius: 28,
|
|
258
|
+
zIndex: 1000,
|
|
208
259
|
}, children: [_jsxs("div", { style: {
|
|
209
260
|
position: "absolute",
|
|
210
261
|
left: "50%",
|
|
@@ -280,7 +331,125 @@ function MobileInner(props) {
|
|
|
280
331
|
boxShadow: recordingChoice === "transcribe"
|
|
281
332
|
? "0 16px 28px rgba(109, 74, 255, 0.24)"
|
|
282
333
|
: "none",
|
|
283
|
-
}, children: "\u8F6C\u6587\u5B57" })] })] })) : null,
|
|
334
|
+
}, children: "\u8F6C\u6587\u5B57" })] })] })) : null, isTranscriptConfirmVisible ? (_jsxs("div", { style: {
|
|
335
|
+
position: "fixed",
|
|
336
|
+
inset: 0,
|
|
337
|
+
zIndex: 1001,
|
|
338
|
+
display: "grid",
|
|
339
|
+
gridTemplateRows: "minmax(0, 1fr) auto",
|
|
340
|
+
padding: "max(20px, env(safe-area-inset-top, 0px)) 16px calc(28px + env(safe-area-inset-bottom, 0px))",
|
|
341
|
+
background: "rgba(10, 12, 16, 0.68)",
|
|
342
|
+
backdropFilter: "blur(1px)",
|
|
343
|
+
}, children: [_jsx("div", { style: {
|
|
344
|
+
minHeight: 0,
|
|
345
|
+
display: "grid",
|
|
346
|
+
alignContent: "end",
|
|
347
|
+
paddingBottom: 72,
|
|
348
|
+
}, children: _jsxs("div", { style: {
|
|
349
|
+
justifySelf: "center",
|
|
350
|
+
position: "relative",
|
|
351
|
+
width: "min(100%, 524px)",
|
|
352
|
+
minHeight: 88,
|
|
353
|
+
padding: "24px 26px",
|
|
354
|
+
borderRadius: tokens.radius.inputSurface + 8,
|
|
355
|
+
background: transcriptConfirmSurface,
|
|
356
|
+
border: `1px solid ${tokens.color.borderFocus}`,
|
|
357
|
+
color: tokens.color.textPrimary,
|
|
358
|
+
fontSize: Math.max(20, tokens.typography.inputText.size + 7),
|
|
359
|
+
lineHeight: "34px",
|
|
360
|
+
fontWeight: 680,
|
|
361
|
+
boxShadow: tokens.shadow.inputElevated,
|
|
362
|
+
display: "grid",
|
|
363
|
+
}, children: [_jsx("textarea", { "aria-label": "\u7F16\u8F91\u8F6C\u5199\u6587\u672C", value: transcriptDraft, onChange: (event) => {
|
|
364
|
+
setTranscriptDraft(event.target.value);
|
|
365
|
+
}, style: {
|
|
366
|
+
width: "100%",
|
|
367
|
+
minHeight: 40,
|
|
368
|
+
maxHeight: 180,
|
|
369
|
+
resize: "none",
|
|
370
|
+
border: 0,
|
|
371
|
+
outline: "none",
|
|
372
|
+
background: "transparent",
|
|
373
|
+
color: "inherit",
|
|
374
|
+
font: "inherit",
|
|
375
|
+
lineHeight: "inherit",
|
|
376
|
+
padding: 0,
|
|
377
|
+
margin: 0,
|
|
378
|
+
overflowWrap: "anywhere",
|
|
379
|
+
} }), _jsx("span", { "aria-hidden": "true", style: {
|
|
380
|
+
position: "absolute",
|
|
381
|
+
right: 72,
|
|
382
|
+
bottom: -10,
|
|
383
|
+
width: 22,
|
|
384
|
+
height: 22,
|
|
385
|
+
borderRadius: 4,
|
|
386
|
+
background: transcriptConfirmSurface,
|
|
387
|
+
borderRight: `1px solid ${tokens.color.borderFocus}`,
|
|
388
|
+
borderBottom: `1px solid ${tokens.color.borderFocus}`,
|
|
389
|
+
transform: "rotate(45deg)",
|
|
390
|
+
} })] }) }), _jsxs("div", { style: {
|
|
391
|
+
display: "grid",
|
|
392
|
+
gridTemplateColumns: "92px 112px minmax(120px, 176px)",
|
|
393
|
+
justifyContent: "space-between",
|
|
394
|
+
alignItems: "end",
|
|
395
|
+
gap: 16,
|
|
396
|
+
width: "min(100%, 524px)",
|
|
397
|
+
justifySelf: "center",
|
|
398
|
+
}, children: [_jsxs("button", { type: "button", "aria-label": "\u53D6\u6D88\u8F6C\u6587\u5B57", onClick: () => {
|
|
399
|
+
voice.clearTranscriptPreview();
|
|
400
|
+
}, style: {
|
|
401
|
+
border: 0,
|
|
402
|
+
background: "transparent",
|
|
403
|
+
color: tokens.color.textSecondary,
|
|
404
|
+
display: "grid",
|
|
405
|
+
justifyItems: "center",
|
|
406
|
+
gap: 10,
|
|
407
|
+
fontSize: 14,
|
|
408
|
+
lineHeight: "20px",
|
|
409
|
+
fontWeight: 500,
|
|
410
|
+
}, children: [_jsx("span", { style: {
|
|
411
|
+
width: 64,
|
|
412
|
+
height: 64,
|
|
413
|
+
borderRadius: "50%",
|
|
414
|
+
display: "grid",
|
|
415
|
+
placeItems: "center",
|
|
416
|
+
background: transcriptConfirmActionSurface,
|
|
417
|
+
border: `1px solid ${transcriptConfirmActionBorder}`,
|
|
418
|
+
color: tokens.color.textPrimary,
|
|
419
|
+
fontSize: 34,
|
|
420
|
+
lineHeight: 1,
|
|
421
|
+
}, children: "\u00D7" }), "\u53D6\u6D88"] }), _jsxs("button", { type: "button", "aria-label": "\u53D1\u9001\u539F\u8BED\u97F3", disabled: !voice.pendingTranscriptBlob || props.disabled, onClick: () => void sendConfirmedOriginalVoice(), style: {
|
|
422
|
+
border: 0,
|
|
423
|
+
background: "transparent",
|
|
424
|
+
color: tokens.color.textSecondary,
|
|
425
|
+
display: "grid",
|
|
426
|
+
justifyItems: "center",
|
|
427
|
+
gap: 10,
|
|
428
|
+
fontSize: 14,
|
|
429
|
+
lineHeight: "20px",
|
|
430
|
+
fontWeight: 500,
|
|
431
|
+
opacity: voice.pendingTranscriptBlob && !props.disabled ? 1 : 0.45,
|
|
432
|
+
}, children: [_jsx("span", { style: {
|
|
433
|
+
width: 64,
|
|
434
|
+
height: 64,
|
|
435
|
+
borderRadius: "50%",
|
|
436
|
+
display: "grid",
|
|
437
|
+
placeItems: "center",
|
|
438
|
+
background: transcriptConfirmActionSurface,
|
|
439
|
+
border: `1px solid ${transcriptConfirmActionBorder}`,
|
|
440
|
+
color: tokens.color.textPrimary,
|
|
441
|
+
}, children: _jsx(MicOutlineIcon, { size: 25, strokeWidth: 2 }) }), "\u53D1\u9001\u539F\u8BED\u97F3"] }), _jsx("button", { type: "button", disabled: props.disabled || !transcriptDraft.trim(), onClick: () => void sendConfirmedTranscript(), style: {
|
|
442
|
+
border: 0,
|
|
443
|
+
minHeight: 64,
|
|
444
|
+
borderRadius: 999,
|
|
445
|
+
padding: "0 28px",
|
|
446
|
+
background: tokens.color.actionSendDefault,
|
|
447
|
+
color: tokens.color.actionSendTextOnPrimary,
|
|
448
|
+
fontSize: 17,
|
|
449
|
+
lineHeight: "24px",
|
|
450
|
+
fontWeight: 700,
|
|
451
|
+
opacity: props.disabled || !transcriptDraft.trim() ? 0.5 : 1,
|
|
452
|
+
}, children: "\u53D1\u9001" })] })] })) : null, _jsx(AttachmentPreview, { attachments: attachments.attachments, tokens: tokens }), _jsx("input", { ref: imageInputRef, type: "file", accept: props.image?.accept ?? "image/*", style: { display: "none" }, onChange: (event) => {
|
|
284
453
|
const file = event.target.files?.[0];
|
|
285
454
|
if (file) {
|
|
286
455
|
void handleImageFile(file);
|
|
@@ -5,8 +5,10 @@ export declare function useVoiceInputController(config?: ChatVoiceConfig): {
|
|
|
5
5
|
status: import("..").ChatInputStatus;
|
|
6
6
|
waveformLevels: number[];
|
|
7
7
|
durationMs: number;
|
|
8
|
+
pendingTranscriptBlob: Blob | null;
|
|
8
9
|
beginVoiceMode: () => void;
|
|
9
10
|
setMode: (args_0: import("..").ChatInputMode | ((prev: import("..").ChatInputMode) => import("..").ChatInputMode)) => void;
|
|
11
|
+
clearTranscriptPreview: () => void;
|
|
10
12
|
startRecording: () => void;
|
|
11
13
|
cancelRecording: (message?: string | null) => void;
|
|
12
14
|
stopRecording: (action?: StopAction) => void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useAtom } from "jotai";
|
|
2
|
-
import { useEffect, useRef } from "react";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import { waveformLevelsAtom, recordingDurationMsAtom } from "../atoms/voiceAtoms";
|
|
4
4
|
import { insertTranscriptText } from "./useTranscriptComposer";
|
|
5
5
|
import { useChatInputState } from "./useChatInputState";
|
|
@@ -12,6 +12,7 @@ export function useVoiceInputController(config) {
|
|
|
12
12
|
const managerRef = useRef(null);
|
|
13
13
|
const stopActionRef = useRef("send");
|
|
14
14
|
const abortMessageRef = useRef(null);
|
|
15
|
+
const [pendingTranscriptBlob, setPendingTranscriptBlob] = useState(null);
|
|
15
16
|
const [levels, setLevels] = useAtom(waveformLevelsAtom);
|
|
16
17
|
const [durationMs, setDurationMs] = useAtom(recordingDurationMsAtom);
|
|
17
18
|
const { mode, setMode, status, setStatus, text, setText, setTranscript, setError, } = useChatInputState();
|
|
@@ -83,6 +84,7 @@ export function useVoiceInputController(config) {
|
|
|
83
84
|
return;
|
|
84
85
|
}
|
|
85
86
|
if (action === "send") {
|
|
87
|
+
setPendingTranscriptBlob(null);
|
|
86
88
|
setTranscript("");
|
|
87
89
|
setDurationMs(0);
|
|
88
90
|
setStatus(modeRef.current === "voice" ? "voice-mode-ready" : "idle");
|
|
@@ -94,7 +96,10 @@ export function useVoiceInputController(config) {
|
|
|
94
96
|
return;
|
|
95
97
|
}
|
|
96
98
|
setTranscript(nextTranscript);
|
|
97
|
-
|
|
99
|
+
setPendingTranscriptBlob(blob);
|
|
100
|
+
if (!configRef.current?.confirmTranscription) {
|
|
101
|
+
setText(insertTranscriptText(textRef.current, nextTranscript));
|
|
102
|
+
}
|
|
98
103
|
setDurationMs(0);
|
|
99
104
|
setStatus("transcript-ready");
|
|
100
105
|
setError(null);
|
|
@@ -105,6 +110,7 @@ export function useVoiceInputController(config) {
|
|
|
105
110
|
}
|
|
106
111
|
}
|
|
107
112
|
function finishVoiceSend(blob) {
|
|
113
|
+
setPendingTranscriptBlob(null);
|
|
108
114
|
setTranscript("");
|
|
109
115
|
setStatus(modeRef.current === "voice" ? "voice-mode-ready" : "idle");
|
|
110
116
|
setError(null);
|
|
@@ -115,12 +121,20 @@ export function useVoiceInputController(config) {
|
|
|
115
121
|
setStatus("voice-mode-ready");
|
|
116
122
|
setError(null);
|
|
117
123
|
}
|
|
124
|
+
function clearTranscriptPreview() {
|
|
125
|
+
setPendingTranscriptBlob(null);
|
|
126
|
+
setTranscript("");
|
|
127
|
+
setDurationMs(0);
|
|
128
|
+
setStatus(modeRef.current === "voice" ? "voice-mode-ready" : "idle");
|
|
129
|
+
setError(null);
|
|
130
|
+
}
|
|
118
131
|
function startRecording() {
|
|
119
132
|
if (status === "recording") {
|
|
120
133
|
return;
|
|
121
134
|
}
|
|
122
135
|
setStatus("recording");
|
|
123
136
|
setError(null);
|
|
137
|
+
setPendingTranscriptBlob(null);
|
|
124
138
|
setTranscript("");
|
|
125
139
|
setDurationMs(0);
|
|
126
140
|
setLevels(DEFAULT_WAVEFORM);
|
|
@@ -145,8 +159,10 @@ export function useVoiceInputController(config) {
|
|
|
145
159
|
status,
|
|
146
160
|
waveformLevels: levels,
|
|
147
161
|
durationMs,
|
|
162
|
+
pendingTranscriptBlob,
|
|
148
163
|
beginVoiceMode,
|
|
149
164
|
setMode,
|
|
165
|
+
clearTranscriptPreview,
|
|
150
166
|
startRecording,
|
|
151
167
|
cancelRecording,
|
|
152
168
|
stopRecording,
|
package/lib/types/voice.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export type ChatVoiceConfig = {
|
|
|
12
12
|
lang?: string;
|
|
13
13
|
minDurationMs?: number;
|
|
14
14
|
transcribeTimeoutMs?: number;
|
|
15
|
+
confirmTranscription?: boolean;
|
|
15
16
|
simulatedTranscript?: string;
|
|
16
17
|
shortRecordingMessage?: string;
|
|
17
18
|
onVoiceSend?: (blob: Blob) => Promise<void> | void;
|