@pmate/chat-sdk 0.1.10 → 0.1.12
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/ChatInputDesktop.js +1 -0
- package/lib/components/ChatInputMobile.js +29 -25
- package/lib/components/HoldToTalkButton.js +4 -0
- package/lib/components/WaveformFeedback.js +0 -1
- package/lib/hooks/useVoiceInputController.js +25 -6
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/runtime/index.d.ts +1 -0
- package/lib/runtime/index.js +1 -0
- package/lib/runtime/readerClient.d.ts +97 -0
- package/lib/runtime/readerClient.js +91 -0
- package/lib/types/voice.d.ts +4 -0
- package/package.json +1 -1
|
@@ -30,6 +30,7 @@ function DesktopInner(props) {
|
|
|
30
30
|
const fileInputRef = useRef(null);
|
|
31
31
|
const voice = useVoiceInputController({
|
|
32
32
|
...props.voice,
|
|
33
|
+
onVoiceTranscript: props.voice?.onVoiceTranscript,
|
|
33
34
|
onVoiceSend: async (blob) => {
|
|
34
35
|
await props.voice?.onVoiceSend?.(blob);
|
|
35
36
|
await props.onSend?.({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { Provider, createStore } from "jotai";
|
|
4
4
|
import { useEffect, useRef, useState } from "react";
|
|
@@ -35,6 +35,7 @@ function MobileInner(props) {
|
|
|
35
35
|
const fileInputRef = useRef(null);
|
|
36
36
|
const voice = useVoiceInputController({
|
|
37
37
|
...props.voice,
|
|
38
|
+
onVoiceTranscript: props.voice?.onVoiceTranscript,
|
|
38
39
|
onVoiceSend: async (blob) => {
|
|
39
40
|
await props.onSend?.({
|
|
40
41
|
text: "",
|
|
@@ -196,14 +197,16 @@ function MobileInner(props) {
|
|
|
196
197
|
boxShadow: "none",
|
|
197
198
|
overflow: "visible",
|
|
198
199
|
}, children: [isRecordingOverlayVisible ? (_jsxs("div", { style: {
|
|
199
|
-
position: "
|
|
200
|
-
left:
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
position: "fixed",
|
|
201
|
+
left: "50%",
|
|
202
|
+
bottom: "calc(72px + env(safe-area-inset-bottom, 0px))",
|
|
203
|
+
width: "min(420px, calc(100vw - 16px))",
|
|
204
|
+
transform: "translateX(-50%)",
|
|
203
205
|
height: 250,
|
|
204
206
|
pointerEvents: "none",
|
|
205
207
|
background: "linear-gradient(180deg, rgba(22, 16, 45, 0) 0%, rgba(22, 16, 45, 0.24) 34%, rgba(22, 16, 45, 0.6) 100%)",
|
|
206
208
|
borderRadius: 28,
|
|
209
|
+
zIndex: 1000,
|
|
207
210
|
}, children: [_jsxs("div", { style: {
|
|
208
211
|
position: "absolute",
|
|
209
212
|
left: "50%",
|
|
@@ -313,7 +316,7 @@ function MobileInner(props) {
|
|
|
313
316
|
gridTemplateColumns: "30px minmax(0, 1fr) 30px 30px",
|
|
314
317
|
gap: 4,
|
|
315
318
|
alignItems: "center",
|
|
316
|
-
padding:
|
|
319
|
+
padding: 0,
|
|
317
320
|
}, children: [_jsx(EmojiPicker, { open: isEmojiOpen, isMobile: true, anchorRef: emojiAnchorRef, onSelect: insertEmoji, onClose: () => setIsEmojiOpen(false) }), _jsx("button", { type: "button", "aria-label": state.mode === "voice" ? "切换到文字输入" : "切换到语音输入", disabled: props.disabled || props.enableVoice === false, onClick: () => {
|
|
318
321
|
const next = state.mode === "voice" ? "text" : "voice";
|
|
319
322
|
state.setMode(next);
|
|
@@ -325,6 +328,7 @@ function MobileInner(props) {
|
|
|
325
328
|
: tokens.color.iconDefault,
|
|
326
329
|
opacity: props.disabled ? 0.45 : 1,
|
|
327
330
|
}, children: state.mode === "voice" ? (_jsx(KeyboardOutlineIcon, { size: 27 })) : (_jsx(MicOutlineIcon, { size: 24, strokeWidth: 2 })) }), _jsx(InputSurface, { tokens: tokens, status: state.status, styleOverride: {
|
|
331
|
+
gridColumn: state.mode === "voice" ? "2 / 5" : undefined,
|
|
328
332
|
borderRadius: tokens.radius.inputSurface,
|
|
329
333
|
background: state.status === "error"
|
|
330
334
|
? tokens.color.surfaceInputError
|
|
@@ -342,8 +346,8 @@ function MobileInner(props) {
|
|
|
342
346
|
minHeight: 48,
|
|
343
347
|
display: "flex",
|
|
344
348
|
alignItems: "center",
|
|
345
|
-
padding: state.mode === "voice" ?
|
|
346
|
-
}, children: state.mode === "voice" ? (_jsx("div", { style: { minHeight: 48 }, children: _jsx(HoldToTalkButton, { tokens: tokens, active: isRecordingOverlayVisible, disabled: props.disabled, text: "\u6309\u4F4F \u8BF4\u8BDD", activeText: "\u677E\u5F00 \u53D1\u9001", onPressStart: () => {
|
|
349
|
+
padding: state.mode === "voice" ? 0 : "0 12px",
|
|
350
|
+
}, children: state.mode === "voice" ? (_jsx("div", { style: { minHeight: 48, width: "100%" }, children: _jsx(HoldToTalkButton, { tokens: tokens, active: isRecordingOverlayVisible, disabled: props.disabled, text: "\u6309\u4F4F \u8BF4\u8BDD", activeText: "\u677E\u5F00 \u53D1\u9001", onPressStart: () => {
|
|
347
351
|
setRecordingChoice("send");
|
|
348
352
|
voice.startRecording();
|
|
349
353
|
}, onPressMove: (event) => {
|
|
@@ -386,23 +390,23 @@ function MobileInner(props) {
|
|
|
386
390
|
fontSize: tokens.typography.inputText.size,
|
|
387
391
|
lineHeight: `${tokens.typography.inputText.lineHeight}px`,
|
|
388
392
|
caretColor: tokens.color.brandPrimary,
|
|
389
|
-
} })) }), _jsx(EmojiTrigger, { ref: emojiAnchorRef, "data-icon-size": 24, onClick: () => {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
393
|
+
} })) }), state.mode === "voice" ? null : (_jsxs(_Fragment, { children: [_jsx(EmojiTrigger, { ref: emojiAnchorRef, "data-icon-size": 24, onClick: () => {
|
|
394
|
+
setIsMoreOpen(false);
|
|
395
|
+
setIsEmojiOpen((prev) => !prev);
|
|
396
|
+
}, style: {
|
|
397
|
+
...sideActionStyle,
|
|
398
|
+
width: 30,
|
|
399
|
+
height: 30,
|
|
400
|
+
} }), _jsx(MoreTrigger, { "data-icon-size": 24, onClick: () => {
|
|
401
|
+
setIsEmojiOpen(false);
|
|
402
|
+
setIsMoreOpen((prev) => !prev);
|
|
403
|
+
}, disabled: props.disabled, style: {
|
|
404
|
+
...sideActionStyle,
|
|
405
|
+
color: isMoreOpen ? tokens.color.iconActive : tokens.color.iconDefault,
|
|
406
|
+
width: 30,
|
|
407
|
+
height: 30,
|
|
408
|
+
opacity: props.disabled ? 0.4 : 1,
|
|
409
|
+
} })] }))] }), _jsx(StatusHint, { tokens: tokens, text: state.status === "recording" || state.status === "transcribing" ? undefined : statusText, tone: state.status === "error" ? "error" : "secondary" })] }));
|
|
406
410
|
}
|
|
407
411
|
export function ChatInputMobile(props) {
|
|
408
412
|
const stateKey = JSON.stringify({
|
|
@@ -24,6 +24,10 @@ export function HoldToTalkButton({ tokens, active, disabled, text = "按住 说
|
|
|
24
24
|
boxShadow: "none",
|
|
25
25
|
padding: "0 16px",
|
|
26
26
|
minHeight: 48,
|
|
27
|
+
height: "100%",
|
|
28
|
+
display: "flex",
|
|
29
|
+
alignItems: "center",
|
|
30
|
+
justifyContent: "center",
|
|
27
31
|
fontFamily: tokens.typography.familyBase,
|
|
28
32
|
fontSize: tokens.typography.voiceHoldToTalk.size,
|
|
29
33
|
lineHeight: `${tokens.typography.voiceHoldToTalk.lineHeight}px`,
|
|
@@ -8,7 +8,6 @@ export function WaveformFeedback({ levels, tokens, compact = false, }) {
|
|
|
8
8
|
justifyContent: "center",
|
|
9
9
|
gap: compact ? 2 : 3,
|
|
10
10
|
minHeight: compact ? 24 : 30,
|
|
11
|
-
filter: compact ? "blur(0.15px)" : "blur(0.1px)",
|
|
12
11
|
}, children: normalizedLevels.map((level, index) => {
|
|
13
12
|
const distanceFromCenter = Math.abs(index - (normalizedLevels.length - 1) / 2);
|
|
14
13
|
const centerWeight = 1 - distanceFromCenter / ((normalizedLevels.length + 1) / 2);
|
|
@@ -36,13 +36,15 @@ export function useVoiceInputController(config) {
|
|
|
36
36
|
setDurationMs(0);
|
|
37
37
|
if (stopActionRef.current === "transcribe") {
|
|
38
38
|
setStatus("transcribing");
|
|
39
|
-
void transcribeBlob(blob);
|
|
39
|
+
void transcribeBlob(blob, "transcribe");
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
if (configRef.current?.asrClient || configRef.current?.simulatedTranscript) {
|
|
43
|
+
setStatus("transcribing");
|
|
44
|
+
void transcribeBlob(blob, "send");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
finishVoiceSend(blob);
|
|
46
48
|
});
|
|
47
49
|
const unsubAbort = manager.on(MicEvents.Abort, () => {
|
|
48
50
|
setLevels(DEFAULT_WAVEFORM);
|
|
@@ -67,7 +69,7 @@ export function useVoiceInputController(config) {
|
|
|
67
69
|
managerRef.current = null;
|
|
68
70
|
};
|
|
69
71
|
}, []);
|
|
70
|
-
async function transcribeBlob(blob) {
|
|
72
|
+
async function transcribeBlob(blob, action) {
|
|
71
73
|
try {
|
|
72
74
|
const transcribeTimeoutMs = configRef.current?.transcribeTimeoutMs ?? DEFAULT_TRANSCRIBE_TIMEOUT_MS;
|
|
73
75
|
const nextTranscript = configRef.current?.asrClient
|
|
@@ -80,6 +82,17 @@ export function useVoiceInputController(config) {
|
|
|
80
82
|
setError("No speech was recognized.");
|
|
81
83
|
return;
|
|
82
84
|
}
|
|
85
|
+
if (action === "send") {
|
|
86
|
+
setTranscript("");
|
|
87
|
+
setDurationMs(0);
|
|
88
|
+
setStatus(modeRef.current === "voice" ? "voice-mode-ready" : "idle");
|
|
89
|
+
setError(null);
|
|
90
|
+
await configRef.current?.onVoiceTranscript?.(nextTranscript, {
|
|
91
|
+
blob,
|
|
92
|
+
action,
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
83
96
|
setTranscript(nextTranscript);
|
|
84
97
|
setText(insertTranscriptText(textRef.current, nextTranscript));
|
|
85
98
|
setDurationMs(0);
|
|
@@ -91,6 +104,12 @@ export function useVoiceInputController(config) {
|
|
|
91
104
|
setError(error instanceof Error ? error.message : String(error));
|
|
92
105
|
}
|
|
93
106
|
}
|
|
107
|
+
function finishVoiceSend(blob) {
|
|
108
|
+
setTranscript("");
|
|
109
|
+
setStatus(modeRef.current === "voice" ? "voice-mode-ready" : "idle");
|
|
110
|
+
setError(null);
|
|
111
|
+
void configRef.current?.onVoiceSend?.(blob);
|
|
112
|
+
}
|
|
94
113
|
function beginVoiceMode() {
|
|
95
114
|
setMode("voice");
|
|
96
115
|
setStatus("voice-mode-ready");
|
package/lib/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export * from "./services/authTokenResolver";
|
|
|
19
19
|
export * from "./runtime/botAuth";
|
|
20
20
|
export * from "./runtime/botKey";
|
|
21
21
|
export * from "./runtime/chatClient";
|
|
22
|
+
export * from "./runtime/readerClient";
|
|
22
23
|
export * from "./runtime/thread";
|
|
23
24
|
export * from "./tokens/defaultTokens";
|
|
24
25
|
export * from "./tokens/chatInputThemes";
|
package/lib/index.js
CHANGED
|
@@ -19,6 +19,7 @@ export * from "./services/authTokenResolver";
|
|
|
19
19
|
export * from "./runtime/botAuth";
|
|
20
20
|
export * from "./runtime/botKey";
|
|
21
21
|
export * from "./runtime/chatClient";
|
|
22
|
+
export * from "./runtime/readerClient";
|
|
22
23
|
export * from "./runtime/thread";
|
|
23
24
|
export * from "./tokens/defaultTokens";
|
|
24
25
|
export * from "./tokens/chatInputThemes";
|
package/lib/runtime/index.d.ts
CHANGED
package/lib/runtime/index.js
CHANGED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export type ChatReaderClientOptions = {
|
|
2
|
+
baseUrl?: string;
|
|
3
|
+
appId: string;
|
|
4
|
+
chainId?: string;
|
|
5
|
+
socialChainId?: string;
|
|
6
|
+
fetchImpl?: typeof fetch;
|
|
7
|
+
};
|
|
8
|
+
export type ChatReaderEnvelope<T> = {
|
|
9
|
+
success?: boolean;
|
|
10
|
+
data?: T;
|
|
11
|
+
message?: string;
|
|
12
|
+
};
|
|
13
|
+
export type ChatReaderMessage = {
|
|
14
|
+
hash: string;
|
|
15
|
+
t: number;
|
|
16
|
+
from: string;
|
|
17
|
+
to: string;
|
|
18
|
+
opcode: number;
|
|
19
|
+
kind: number;
|
|
20
|
+
body: unknown;
|
|
21
|
+
recipients?: string[];
|
|
22
|
+
};
|
|
23
|
+
export type ChatReaderThread = {
|
|
24
|
+
threadHash: string;
|
|
25
|
+
type: "dm" | "group";
|
|
26
|
+
associatedId: string;
|
|
27
|
+
lastUpdateAt: number;
|
|
28
|
+
name: string;
|
|
29
|
+
avatar: string;
|
|
30
|
+
lastMessage: ChatReaderMessage;
|
|
31
|
+
total: number;
|
|
32
|
+
unread: number;
|
|
33
|
+
};
|
|
34
|
+
export type ChatReaderContact = {
|
|
35
|
+
id?: string;
|
|
36
|
+
accountId?: string;
|
|
37
|
+
peerId?: string;
|
|
38
|
+
type?: "dm" | "group" | "user";
|
|
39
|
+
name?: string;
|
|
40
|
+
nickName?: string;
|
|
41
|
+
title?: string;
|
|
42
|
+
avatar?: string;
|
|
43
|
+
relationship?: string;
|
|
44
|
+
threadId?: string;
|
|
45
|
+
updatedAt?: number;
|
|
46
|
+
members?: string[];
|
|
47
|
+
};
|
|
48
|
+
export type ChatReaderSocialMessage = {
|
|
49
|
+
appId: string;
|
|
50
|
+
threadId: string;
|
|
51
|
+
threadKind: "dm" | "group";
|
|
52
|
+
messageId: string;
|
|
53
|
+
from: string;
|
|
54
|
+
to: string;
|
|
55
|
+
recipients?: string[];
|
|
56
|
+
body: unknown;
|
|
57
|
+
clientCreatedAt?: number;
|
|
58
|
+
serverCreatedAt: number;
|
|
59
|
+
};
|
|
60
|
+
export type ChatReaderMessagePage = {
|
|
61
|
+
page: number;
|
|
62
|
+
totalPage: number;
|
|
63
|
+
pageSize: number;
|
|
64
|
+
data: ChatReaderMessage[];
|
|
65
|
+
};
|
|
66
|
+
export type ChatReaderUnread = {
|
|
67
|
+
appId: string;
|
|
68
|
+
accountId: string;
|
|
69
|
+
threadId: string;
|
|
70
|
+
unreadCount: number;
|
|
71
|
+
};
|
|
72
|
+
export declare class ChatReaderClient {
|
|
73
|
+
private readonly options;
|
|
74
|
+
private readonly baseUrl;
|
|
75
|
+
private readonly chainId;
|
|
76
|
+
private readonly socialChainId;
|
|
77
|
+
private readonly fetchImpl;
|
|
78
|
+
constructor(options: ChatReaderClientOptions);
|
|
79
|
+
listThreads(userId: string): Promise<ChatReaderThread[]>;
|
|
80
|
+
getThread(userId: string, threadHash: string): Promise<ChatReaderThread | null>;
|
|
81
|
+
listSocialContacts(accountId: string): Promise<ChatReaderContact[]>;
|
|
82
|
+
listSocialUnread(accountId: string): Promise<ChatReaderUnread[]>;
|
|
83
|
+
listSocialMessages(threadId: string): Promise<ChatReaderSocialMessage[]>;
|
|
84
|
+
listThreadMessages(input: {
|
|
85
|
+
userId: string;
|
|
86
|
+
threadHash: string;
|
|
87
|
+
page?: number;
|
|
88
|
+
}): Promise<ChatReaderMessagePage | null>;
|
|
89
|
+
markRead(input: {
|
|
90
|
+
userId: string;
|
|
91
|
+
threadHash: string;
|
|
92
|
+
hashes: string[];
|
|
93
|
+
}): Promise<unknown>;
|
|
94
|
+
private indexerRequest;
|
|
95
|
+
private request;
|
|
96
|
+
}
|
|
97
|
+
export declare function createChatReaderClient(options: ChatReaderClientOptions): ChatReaderClient;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const DEFAULT_INDEXER_URL = "https://indexer.pmate.chat";
|
|
2
|
+
const DEFAULT_CHAIN_ID = "pmate";
|
|
3
|
+
const DEFAULT_SOCIAL_CHAIN_ID = "social";
|
|
4
|
+
export class ChatReaderClient {
|
|
5
|
+
options;
|
|
6
|
+
baseUrl;
|
|
7
|
+
chainId;
|
|
8
|
+
socialChainId;
|
|
9
|
+
fetchImpl;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.options = options;
|
|
12
|
+
this.baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_INDEXER_URL);
|
|
13
|
+
this.chainId = options.chainId ?? DEFAULT_CHAIN_ID;
|
|
14
|
+
this.socialChainId = options.socialChainId ?? DEFAULT_SOCIAL_CHAIN_ID;
|
|
15
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
16
|
+
}
|
|
17
|
+
async listThreads(userId) {
|
|
18
|
+
return this.indexerRequest("thread", "threads", { user: userId });
|
|
19
|
+
}
|
|
20
|
+
async getThread(userId, threadHash) {
|
|
21
|
+
return this.indexerRequest("thread", "thread", { user: userId, threadHash });
|
|
22
|
+
}
|
|
23
|
+
async listSocialContacts(accountId) {
|
|
24
|
+
return this.indexerRequest("social_indexer", "contacts", {
|
|
25
|
+
appId: this.options.appId,
|
|
26
|
+
accountId,
|
|
27
|
+
}, undefined, { chainId: this.socialChainId });
|
|
28
|
+
}
|
|
29
|
+
async listSocialUnread(accountId) {
|
|
30
|
+
return this.indexerRequest("social_indexer", "unread", {
|
|
31
|
+
appId: this.options.appId,
|
|
32
|
+
accountId,
|
|
33
|
+
}, undefined, { chainId: this.socialChainId });
|
|
34
|
+
}
|
|
35
|
+
async listSocialMessages(threadId) {
|
|
36
|
+
return this.indexerRequest("social_indexer", "messages", {
|
|
37
|
+
appId: this.options.appId,
|
|
38
|
+
threadId,
|
|
39
|
+
}, undefined, { chainId: this.socialChainId });
|
|
40
|
+
}
|
|
41
|
+
async listThreadMessages(input) {
|
|
42
|
+
return this.indexerRequest("thread", "messages", {
|
|
43
|
+
user: input.userId,
|
|
44
|
+
threadHash: input.threadHash,
|
|
45
|
+
...(input.page !== undefined ? { page: String(input.page) } : {}),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async markRead(input) {
|
|
49
|
+
return this.indexerRequest("thread", "read", {
|
|
50
|
+
user: input.userId,
|
|
51
|
+
threadHash: input.threadHash,
|
|
52
|
+
hashes: JSON.stringify(input.hashes),
|
|
53
|
+
}, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async indexerRequest(indexer, action, query, init, options) {
|
|
58
|
+
const method = init?.method ?? "GET";
|
|
59
|
+
const chainId = options?.chainId ?? this.chainId;
|
|
60
|
+
const path = `/${encodeURIComponent(chainId)}/${encodeURIComponent(indexer)}/${encodeURIComponent(action)}`;
|
|
61
|
+
if (method.toUpperCase() === "POST") {
|
|
62
|
+
return this.request(path, {
|
|
63
|
+
...init,
|
|
64
|
+
method,
|
|
65
|
+
body: JSON.stringify(query),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const search = new URLSearchParams(query);
|
|
69
|
+
return this.request(`${path}?${search.toString()}`, init);
|
|
70
|
+
}
|
|
71
|
+
async request(path, init) {
|
|
72
|
+
const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
73
|
+
...init,
|
|
74
|
+
headers: {
|
|
75
|
+
"content-type": "application/json",
|
|
76
|
+
...(init?.headers ?? {}),
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
const json = (await response.json().catch(() => ({})));
|
|
80
|
+
if (!response.ok || json.success === false) {
|
|
81
|
+
throw new Error(json.message || `chat-rest ${response.status}`);
|
|
82
|
+
}
|
|
83
|
+
return (json.data ?? json);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export function createChatReaderClient(options) {
|
|
87
|
+
return new ChatReaderClient(options);
|
|
88
|
+
}
|
|
89
|
+
function normalizeBaseUrl(baseUrl) {
|
|
90
|
+
return baseUrl.replace(/\/+$/, "");
|
|
91
|
+
}
|
package/lib/types/voice.d.ts
CHANGED
|
@@ -15,4 +15,8 @@ export type ChatVoiceConfig = {
|
|
|
15
15
|
simulatedTranscript?: string;
|
|
16
16
|
shortRecordingMessage?: string;
|
|
17
17
|
onVoiceSend?: (blob: Blob) => Promise<void> | void;
|
|
18
|
+
onVoiceTranscript?: (text: string, context: {
|
|
19
|
+
blob: Blob;
|
|
20
|
+
action: "send" | "transcribe";
|
|
21
|
+
}) => Promise<void> | void;
|
|
18
22
|
};
|