@pmate/chat-sdk 0.1.1
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/README.md +5 -0
- package/dist/assets/ChatInput.stories-6Z6tmO-P.js +922 -0
- package/dist/assets/Color-AVL7NMMY-CdXTB89n.js +1 -0
- package/dist/assets/DocsRenderer-PQXLIZUC-CtoiPcLN.js +1243 -0
- package/dist/assets/client-BG97-bzh.js +9 -0
- package/dist/assets/iframe-CWturJ6d.css +1 -0
- package/dist/assets/iframe-fcxekwVB.js +1071 -0
- package/dist/assets/index-u9_9PRd-.js +1 -0
- package/dist/assets/preload-helper-PPVm8Dsz.js +1 -0
- package/dist/assets/react-18-DKDc3Hbl.js +1 -0
- package/dist/favicon-wrapper.svg +46 -0
- package/dist/favicon.svg +1 -0
- package/dist/iframe.html +687 -0
- package/dist/index.html +148 -0
- package/dist/index.json +1 -0
- 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 +1 -0
- package/dist/sb-addons/docs-1/manager-bundle.js +151 -0
- 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 +971 -0
- 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 +46 -0
- package/dist/sb-common-assets/favicon.svg +1 -0
- 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 +799 -0
- package/dist/sb-manager/globals-runtime.js +69791 -0
- package/dist/sb-manager/globals.js +34 -0
- package/dist/sb-manager/runtime.js +13198 -0
- package/dist/vite-inject-mocker-entry.js +2 -0
- package/lib/atoms/attachmentAtoms.d.ts +4 -0
- package/lib/atoms/attachmentAtoms.js +2 -0
- package/lib/atoms/chatInputAtoms.d.ts +16 -0
- package/lib/atoms/chatInputAtoms.js +6 -0
- package/lib/atoms/voiceAtoms.d.ts +6 -0
- package/lib/atoms/voiceAtoms.js +3 -0
- package/lib/components/AttachmentPreview.d.ts +6 -0
- package/lib/components/AttachmentPreview.js +13 -0
- package/lib/components/ChatInputDesktop.d.ts +2 -0
- package/lib/components/ChatInputDesktop.js +181 -0
- package/lib/components/ChatInputMobile.d.ts +2 -0
- package/lib/components/ChatInputMobile.js +338 -0
- package/lib/components/EmojiPicker.d.ts +10 -0
- package/lib/components/EmojiPicker.js +62 -0
- package/lib/components/EmojiTrigger.d.ts +2 -0
- package/lib/components/EmojiTrigger.js +18 -0
- package/lib/components/HoldToTalkButton.d.ts +13 -0
- package/lib/components/HoldToTalkButton.js +38 -0
- package/lib/components/ImageTrigger.d.ts +2 -0
- package/lib/components/ImageTrigger.js +18 -0
- package/lib/components/InputSurface.d.ts +7 -0
- package/lib/components/InputSurface.js +26 -0
- package/lib/components/SendAction.d.ts +5 -0
- package/lib/components/SendAction.js +18 -0
- package/lib/components/StatusHint.d.ts +6 -0
- package/lib/components/StatusHint.js +16 -0
- package/lib/components/TextComposer.d.ts +5 -0
- package/lib/components/TextComposer.js +16 -0
- package/lib/components/VoiceModeSwitch.d.ts +7 -0
- package/lib/components/VoiceModeSwitch.js +14 -0
- package/lib/components/WaveformFeedback.d.ts +6 -0
- package/lib/components/WaveformFeedback.js +44 -0
- package/lib/components/icons.d.ts +11 -0
- package/lib/components/icons.js +29 -0
- package/lib/hooks/useAttachmentState.d.ts +6 -0
- package/lib/hooks/useAttachmentState.js +21 -0
- package/lib/hooks/useChatInputState.d.ts +14 -0
- package/lib/hooks/useChatInputState.js +25 -0
- package/lib/hooks/useTranscriptComposer.d.ts +1 -0
- package/lib/hooks/useTranscriptComposer.js +11 -0
- package/lib/hooks/useVoiceInputController.d.ts +14 -0
- package/lib/hooks/useVoiceInputController.js +133 -0
- package/lib/index.d.ts +23 -0
- package/lib/index.js +23 -0
- package/lib/recorder-processor.js +20 -0
- package/lib/services/agentSdkASRClient.d.ts +28 -0
- package/lib/services/agentSdkASRClient.js +25 -0
- package/lib/services/authTokenResolver.d.ts +7 -0
- package/lib/services/authTokenResolver.js +7 -0
- package/lib/stories/ChatInput.stories.d.ts +15 -0
- package/lib/stories/ChatInput.stories.js +38 -0
- package/lib/tests/agentSdkASRClient.test.d.ts +1 -0
- package/lib/tests/agentSdkASRClient.test.js +39 -0
- package/lib/tests/defaultTokens.test.d.ts +1 -0
- package/lib/tests/defaultTokens.test.js +16 -0
- package/lib/tests/transcriptComposer.test.d.ts +1 -0
- package/lib/tests/transcriptComposer.test.js +10 -0
- package/lib/tokens/defaultTokens.d.ts +3 -0
- package/lib/tokens/defaultTokens.js +91 -0
- package/lib/types/attachment.d.ts +8 -0
- package/lib/types/attachment.js +1 -0
- package/lib/types/chatInput.d.ts +139 -0
- package/lib/types/chatInput.js +1 -0
- package/lib/types/voice.d.ts +17 -0
- package/lib/types/voice.js +1 -0
- package/lib/voice/MicStateManager.d.ts +43 -0
- package/lib/voice/MicStateManager.js +155 -0
- package/lib/voice/Recorder.d.ts +44 -0
- package/lib/voice/Recorder.js +224 -0
- package/package.json +59 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export const defaultChatInputTokens = {
|
|
2
|
+
color: {
|
|
3
|
+
brandPrimary: "#6D4AFF",
|
|
4
|
+
surfaceInputDefault: "#FFFFFF",
|
|
5
|
+
surfaceInputFocus: "#F7F4FF",
|
|
6
|
+
surfaceInputRecording: "#EEE7FF",
|
|
7
|
+
surfaceInputError: "#FFF1F3",
|
|
8
|
+
textPrimary: "#1F1637",
|
|
9
|
+
textSecondary: "#6F6790",
|
|
10
|
+
iconDefault: "#6F6790",
|
|
11
|
+
iconActive: "#6D4AFF",
|
|
12
|
+
actionSendDefault: "#6D4AFF",
|
|
13
|
+
actionSendDisabled: "#CFC6F6",
|
|
14
|
+
actionSendTextOnPrimary: "#FFFFFF",
|
|
15
|
+
borderDefault: "#E6E0F7",
|
|
16
|
+
borderFocus: "#B9A8FF",
|
|
17
|
+
borderError: "#F2A7B5",
|
|
18
|
+
statusError: "#D14363",
|
|
19
|
+
statusWarning: "#D98E04",
|
|
20
|
+
statusSuccess: "#2E9B62",
|
|
21
|
+
voiceWaveformActive: "#7C5CFF",
|
|
22
|
+
voiceWaveformBackground: "#DCD2FF",
|
|
23
|
+
},
|
|
24
|
+
spacing: {
|
|
25
|
+
inputOuter: 12,
|
|
26
|
+
inputInner: 10,
|
|
27
|
+
actionGap: 8,
|
|
28
|
+
attachmentGap: 8,
|
|
29
|
+
voiceWaveformGap: 4,
|
|
30
|
+
mobileTouchInset: 12,
|
|
31
|
+
desktopToolbarGap: 10,
|
|
32
|
+
statusHintTop: 6,
|
|
33
|
+
},
|
|
34
|
+
radius: {
|
|
35
|
+
inputSurface: 20,
|
|
36
|
+
actionButton: 16,
|
|
37
|
+
attachmentPreview: 14,
|
|
38
|
+
waveformBar: 999,
|
|
39
|
+
mobileHoldToTalk: 18,
|
|
40
|
+
desktopInputSurface: 16,
|
|
41
|
+
},
|
|
42
|
+
motion: {
|
|
43
|
+
pressFeedback: "120ms ease-out",
|
|
44
|
+
waveformPulse: "180ms ease-out",
|
|
45
|
+
stateTransition: "180ms ease",
|
|
46
|
+
transcribingLoading: "900ms linear infinite",
|
|
47
|
+
},
|
|
48
|
+
shadow: {
|
|
49
|
+
inputElevated: "0 6px 24px rgba(95, 70, 180, 0.08)",
|
|
50
|
+
recordingActive: "0 0 0 4px rgba(109, 74, 255, 0.14)",
|
|
51
|
+
},
|
|
52
|
+
typography: {
|
|
53
|
+
familyBase: '"SF Pro Text", "PingFang SC", "Helvetica Neue", sans-serif',
|
|
54
|
+
familyMono: '"SF Mono", "JetBrains Mono", monospace',
|
|
55
|
+
inputText: { size: 15, lineHeight: 22, weight: 400 },
|
|
56
|
+
statusHint: { size: 12, lineHeight: 16, weight: 500 },
|
|
57
|
+
actionLabel: { size: 13, lineHeight: 18, weight: 600 },
|
|
58
|
+
voiceHoldToTalk: { size: 14, lineHeight: 20, weight: 600 },
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
export function mergeChatInputTokens(overrides) {
|
|
62
|
+
return {
|
|
63
|
+
...defaultChatInputTokens,
|
|
64
|
+
...overrides,
|
|
65
|
+
color: { ...defaultChatInputTokens.color, ...overrides?.color },
|
|
66
|
+
spacing: { ...defaultChatInputTokens.spacing, ...overrides?.spacing },
|
|
67
|
+
radius: { ...defaultChatInputTokens.radius, ...overrides?.radius },
|
|
68
|
+
motion: { ...defaultChatInputTokens.motion, ...overrides?.motion },
|
|
69
|
+
shadow: { ...defaultChatInputTokens.shadow, ...overrides?.shadow },
|
|
70
|
+
typography: {
|
|
71
|
+
...defaultChatInputTokens.typography,
|
|
72
|
+
...overrides?.typography,
|
|
73
|
+
inputText: {
|
|
74
|
+
...defaultChatInputTokens.typography.inputText,
|
|
75
|
+
...overrides?.typography?.inputText,
|
|
76
|
+
},
|
|
77
|
+
statusHint: {
|
|
78
|
+
...defaultChatInputTokens.typography.statusHint,
|
|
79
|
+
...overrides?.typography?.statusHint,
|
|
80
|
+
},
|
|
81
|
+
actionLabel: {
|
|
82
|
+
...defaultChatInputTokens.typography.actionLabel,
|
|
83
|
+
...overrides?.typography?.actionLabel,
|
|
84
|
+
},
|
|
85
|
+
voiceHoldToTalk: {
|
|
86
|
+
...defaultChatInputTokens.typography.voiceHoldToTalk,
|
|
87
|
+
...overrides?.typography?.voiceHoldToTalk,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from "react";
|
|
2
|
+
import type { ChatAttachment } from "./attachment";
|
|
3
|
+
import type { ChatVoiceConfig } from "./voice";
|
|
4
|
+
export type DeepPartial<T> = {
|
|
5
|
+
[K in keyof T]?: T[K] extends (...args: never[]) => unknown ? T[K] : T[K] extends Array<infer U> ? Array<DeepPartial<U>> : T[K] extends object ? DeepPartial<T[K]> : T[K];
|
|
6
|
+
};
|
|
7
|
+
export type ChatInputMode = "text" | "voice";
|
|
8
|
+
export type ChatInputStatus = "idle" | "typing" | "voice-mode-ready" | "recording" | "transcribing" | "transcript-ready" | "attachment-selected" | "sending" | "error";
|
|
9
|
+
export type ChatInputColorTokens = {
|
|
10
|
+
brandPrimary: string;
|
|
11
|
+
surfaceInputDefault: string;
|
|
12
|
+
surfaceInputFocus: string;
|
|
13
|
+
surfaceInputRecording: string;
|
|
14
|
+
surfaceInputError: string;
|
|
15
|
+
textPrimary: string;
|
|
16
|
+
textSecondary: string;
|
|
17
|
+
iconDefault: string;
|
|
18
|
+
iconActive: string;
|
|
19
|
+
actionSendDefault: string;
|
|
20
|
+
actionSendDisabled: string;
|
|
21
|
+
actionSendTextOnPrimary: string;
|
|
22
|
+
borderDefault: string;
|
|
23
|
+
borderFocus: string;
|
|
24
|
+
borderError: string;
|
|
25
|
+
statusError: string;
|
|
26
|
+
statusWarning: string;
|
|
27
|
+
statusSuccess: string;
|
|
28
|
+
voiceWaveformActive: string;
|
|
29
|
+
voiceWaveformBackground: string;
|
|
30
|
+
};
|
|
31
|
+
export type ChatInputSpacingTokens = {
|
|
32
|
+
inputOuter: number;
|
|
33
|
+
inputInner: number;
|
|
34
|
+
actionGap: number;
|
|
35
|
+
attachmentGap: number;
|
|
36
|
+
voiceWaveformGap: number;
|
|
37
|
+
mobileTouchInset: number;
|
|
38
|
+
desktopToolbarGap: number;
|
|
39
|
+
statusHintTop: number;
|
|
40
|
+
};
|
|
41
|
+
export type ChatInputRadiusTokens = {
|
|
42
|
+
inputSurface: number;
|
|
43
|
+
actionButton: number;
|
|
44
|
+
attachmentPreview: number;
|
|
45
|
+
waveformBar: number;
|
|
46
|
+
mobileHoldToTalk: number;
|
|
47
|
+
desktopInputSurface: number;
|
|
48
|
+
};
|
|
49
|
+
export type ChatInputMotionTokens = {
|
|
50
|
+
pressFeedback: string;
|
|
51
|
+
waveformPulse: string;
|
|
52
|
+
stateTransition: string;
|
|
53
|
+
transcribingLoading: string;
|
|
54
|
+
};
|
|
55
|
+
export type ChatInputShadowTokens = {
|
|
56
|
+
inputElevated: string;
|
|
57
|
+
recordingActive: string;
|
|
58
|
+
};
|
|
59
|
+
export type ChatInputTypographyScale = {
|
|
60
|
+
size: number;
|
|
61
|
+
lineHeight: number;
|
|
62
|
+
weight: number;
|
|
63
|
+
};
|
|
64
|
+
export type ChatInputTypographyTokens = {
|
|
65
|
+
familyBase: string;
|
|
66
|
+
familyMono: string;
|
|
67
|
+
inputText: ChatInputTypographyScale;
|
|
68
|
+
statusHint: ChatInputTypographyScale;
|
|
69
|
+
actionLabel: ChatInputTypographyScale;
|
|
70
|
+
voiceHoldToTalk: ChatInputTypographyScale;
|
|
71
|
+
};
|
|
72
|
+
export type ChatInputTokens = {
|
|
73
|
+
color: ChatInputColorTokens;
|
|
74
|
+
spacing: ChatInputSpacingTokens;
|
|
75
|
+
radius: ChatInputRadiusTokens;
|
|
76
|
+
motion: ChatInputMotionTokens;
|
|
77
|
+
shadow: ChatInputShadowTokens;
|
|
78
|
+
typography: ChatInputTypographyTokens;
|
|
79
|
+
};
|
|
80
|
+
export type ChatInputSlotProps = {
|
|
81
|
+
status: ChatInputStatus;
|
|
82
|
+
mode: ChatInputMode;
|
|
83
|
+
disabled?: boolean;
|
|
84
|
+
};
|
|
85
|
+
export type ChatInputSlots = {
|
|
86
|
+
sendAction: ComponentType<ChatInputSlotProps>;
|
|
87
|
+
statusHint: ComponentType<ChatInputSlotProps & {
|
|
88
|
+
text?: string;
|
|
89
|
+
}>;
|
|
90
|
+
recordingFeedback: ComponentType<ChatInputSlotProps & {
|
|
91
|
+
children?: ReactNode;
|
|
92
|
+
}>;
|
|
93
|
+
};
|
|
94
|
+
export type ChatInputInitialState = {
|
|
95
|
+
mode?: ChatInputMode;
|
|
96
|
+
status?: ChatInputStatus;
|
|
97
|
+
text?: string;
|
|
98
|
+
transcript?: string;
|
|
99
|
+
error?: string;
|
|
100
|
+
attachments?: ChatAttachment[];
|
|
101
|
+
waveformLevels?: number[];
|
|
102
|
+
};
|
|
103
|
+
export type ChatImageUploadResult = {
|
|
104
|
+
name?: string;
|
|
105
|
+
url: string;
|
|
106
|
+
previewUrl?: string;
|
|
107
|
+
};
|
|
108
|
+
export type ChatImageConfig = {
|
|
109
|
+
accept?: string;
|
|
110
|
+
uploadImage?: (file: File) => Promise<ChatImageUploadResult>;
|
|
111
|
+
};
|
|
112
|
+
export type ChatInputBaseProps = {
|
|
113
|
+
value?: string;
|
|
114
|
+
defaultValue?: string;
|
|
115
|
+
onChange?: (value: string) => void;
|
|
116
|
+
onSend?: (payload: {
|
|
117
|
+
text: string;
|
|
118
|
+
attachments: ChatAttachment[];
|
|
119
|
+
transcript?: string;
|
|
120
|
+
audio?: Blob;
|
|
121
|
+
mode?: "text" | "voice";
|
|
122
|
+
}) => Promise<void> | void;
|
|
123
|
+
enableVoice?: boolean;
|
|
124
|
+
enableEmoji?: boolean;
|
|
125
|
+
enableImage?: boolean;
|
|
126
|
+
disabled?: boolean;
|
|
127
|
+
authToken?: string;
|
|
128
|
+
tokens?: DeepPartial<ChatInputTokens>;
|
|
129
|
+
slots?: Partial<ChatInputSlots>;
|
|
130
|
+
initialState?: ChatInputInitialState;
|
|
131
|
+
voice?: ChatVoiceConfig;
|
|
132
|
+
image?: ChatImageConfig;
|
|
133
|
+
};
|
|
134
|
+
export type ChatInputMobileProps = ChatInputBaseProps & {
|
|
135
|
+
voiceModeDefault?: ChatInputMode;
|
|
136
|
+
};
|
|
137
|
+
export type ChatInputDesktopProps = ChatInputBaseProps & {
|
|
138
|
+
submitShortcut?: "enter" | "meta-enter";
|
|
139
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type ChatVoiceStatus = "idle" | "voice-mode-ready" | "recording" | "transcribing" | "transcript-ready" | "error";
|
|
2
|
+
export type WaveformLevel = number;
|
|
3
|
+
export type ChatASRClient = {
|
|
4
|
+
transcribe(blob: Blob, input?: {
|
|
5
|
+
lang?: string;
|
|
6
|
+
}): Promise<{
|
|
7
|
+
text: string;
|
|
8
|
+
}>;
|
|
9
|
+
};
|
|
10
|
+
export type ChatVoiceConfig = {
|
|
11
|
+
asrClient?: ChatASRClient;
|
|
12
|
+
lang?: string;
|
|
13
|
+
minDurationMs?: number;
|
|
14
|
+
simulatedTranscript?: string;
|
|
15
|
+
shortRecordingMessage?: string;
|
|
16
|
+
onVoiceSend?: (blob: Blob) => Promise<void> | void;
|
|
17
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export declare enum MicState {
|
|
2
|
+
Idle = 0,
|
|
3
|
+
Initializing = 1,
|
|
4
|
+
Recording = 2
|
|
5
|
+
}
|
|
6
|
+
export declare enum MicEvents {
|
|
7
|
+
StateChanged = "stateChanged",
|
|
8
|
+
Data = "data",
|
|
9
|
+
Level = "level",
|
|
10
|
+
Finished = "finished",
|
|
11
|
+
Abort = "abort",
|
|
12
|
+
Error = "error"
|
|
13
|
+
}
|
|
14
|
+
type MicEventMap = {
|
|
15
|
+
[MicEvents.StateChanged]: (payload: {
|
|
16
|
+
state: MicState;
|
|
17
|
+
}) => void;
|
|
18
|
+
[MicEvents.Data]: (data: Blob) => void;
|
|
19
|
+
[MicEvents.Level]: (levels: number[]) => void;
|
|
20
|
+
[MicEvents.Finished]: (data: Blob) => void;
|
|
21
|
+
[MicEvents.Abort]: () => void;
|
|
22
|
+
[MicEvents.Error]: (error: Error) => void;
|
|
23
|
+
};
|
|
24
|
+
export declare class MicStateManager {
|
|
25
|
+
private recorder;
|
|
26
|
+
private state;
|
|
27
|
+
private chunks;
|
|
28
|
+
private stream;
|
|
29
|
+
private listeners;
|
|
30
|
+
private initialized;
|
|
31
|
+
on<K extends MicEvents>(event: K, handler: MicEventMap[K]): () => void;
|
|
32
|
+
private emit;
|
|
33
|
+
init(): void;
|
|
34
|
+
start(): Promise<void>;
|
|
35
|
+
stop(): void;
|
|
36
|
+
abort(): void;
|
|
37
|
+
dispose(): void;
|
|
38
|
+
private reset;
|
|
39
|
+
private pauseStream;
|
|
40
|
+
private releaseStream;
|
|
41
|
+
private setState;
|
|
42
|
+
}
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Recorder, RecorderEvents } from "./Recorder";
|
|
2
|
+
export var MicState;
|
|
3
|
+
(function (MicState) {
|
|
4
|
+
MicState[MicState["Idle"] = 0] = "Idle";
|
|
5
|
+
MicState[MicState["Initializing"] = 1] = "Initializing";
|
|
6
|
+
MicState[MicState["Recording"] = 2] = "Recording";
|
|
7
|
+
})(MicState || (MicState = {}));
|
|
8
|
+
export var MicEvents;
|
|
9
|
+
(function (MicEvents) {
|
|
10
|
+
MicEvents["StateChanged"] = "stateChanged";
|
|
11
|
+
MicEvents["Data"] = "data";
|
|
12
|
+
MicEvents["Level"] = "level";
|
|
13
|
+
MicEvents["Finished"] = "finished";
|
|
14
|
+
MicEvents["Abort"] = "abort";
|
|
15
|
+
MicEvents["Error"] = "error";
|
|
16
|
+
})(MicEvents || (MicEvents = {}));
|
|
17
|
+
export class MicStateManager {
|
|
18
|
+
recorder = new Recorder();
|
|
19
|
+
state = MicState.Idle;
|
|
20
|
+
chunks = [];
|
|
21
|
+
stream;
|
|
22
|
+
listeners = {
|
|
23
|
+
[MicEvents.StateChanged]: new Set(),
|
|
24
|
+
[MicEvents.Data]: new Set(),
|
|
25
|
+
[MicEvents.Level]: new Set(),
|
|
26
|
+
[MicEvents.Finished]: new Set(),
|
|
27
|
+
[MicEvents.Abort]: new Set(),
|
|
28
|
+
[MicEvents.Error]: new Set(),
|
|
29
|
+
};
|
|
30
|
+
initialized = false;
|
|
31
|
+
on(event, handler) {
|
|
32
|
+
this.listeners[event].add(handler);
|
|
33
|
+
return () => {
|
|
34
|
+
this.listeners[event].delete(handler);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
emit(event, ...args) {
|
|
38
|
+
for (const handler of this.listeners[event]) {
|
|
39
|
+
;
|
|
40
|
+
handler(...args);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
init() {
|
|
44
|
+
if (this.initialized) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
this.initialized = true;
|
|
48
|
+
this.recorder.on(RecorderEvents.Data, (data) => {
|
|
49
|
+
this.chunks.push(data);
|
|
50
|
+
this.emit(MicEvents.Data, data);
|
|
51
|
+
});
|
|
52
|
+
this.recorder.on(RecorderEvents.Level, (levels) => {
|
|
53
|
+
this.emit(MicEvents.Level, levels);
|
|
54
|
+
});
|
|
55
|
+
this.recorder.on(RecorderEvents.Stop, () => {
|
|
56
|
+
const blob = new Blob(this.chunks, { type: "audio/pcm;rate=16000" });
|
|
57
|
+
if (blob.size < 1024 * 8) {
|
|
58
|
+
this.abort();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
this.emit(MicEvents.Finished, blob);
|
|
62
|
+
this.reset();
|
|
63
|
+
});
|
|
64
|
+
this.recorder.on(RecorderEvents.Error, (error) => {
|
|
65
|
+
this.abort();
|
|
66
|
+
this.emit(MicEvents.Error, error);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async start() {
|
|
70
|
+
if (this.state === MicState.Initializing || this.state === MicState.Recording) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this.setState(MicState.Initializing);
|
|
74
|
+
try {
|
|
75
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
76
|
+
throw new Error("Microphone access is not available in this browser.");
|
|
77
|
+
}
|
|
78
|
+
this.releaseStream();
|
|
79
|
+
this.stream = await navigator.mediaDevices.getUserMedia({
|
|
80
|
+
audio: {
|
|
81
|
+
echoCancellation: true,
|
|
82
|
+
noiseSuppression: true,
|
|
83
|
+
autoGainControl: true,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
await this.recorder.init(this.stream);
|
|
87
|
+
this.stream.getTracks().forEach((track) => {
|
|
88
|
+
track.enabled = true;
|
|
89
|
+
});
|
|
90
|
+
await this.recorder.startRecording();
|
|
91
|
+
this.setState(MicState.Recording);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
this.reset();
|
|
95
|
+
this.emit(MicEvents.Error, toError(error));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
stop() {
|
|
99
|
+
if (this.state !== MicState.Initializing && this.state !== MicState.Recording) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (this.state === MicState.Initializing) {
|
|
103
|
+
this.abort();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.recorder.stopRecording();
|
|
107
|
+
this.recorder.detachStream();
|
|
108
|
+
this.pauseStream();
|
|
109
|
+
this.setState(MicState.Idle);
|
|
110
|
+
}
|
|
111
|
+
abort() {
|
|
112
|
+
if (this.state === MicState.Idle) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
this.recorder.stopRecording();
|
|
116
|
+
this.recorder.detachStream();
|
|
117
|
+
this.pauseStream();
|
|
118
|
+
this.emit(MicEvents.Abort);
|
|
119
|
+
this.reset();
|
|
120
|
+
}
|
|
121
|
+
dispose() {
|
|
122
|
+
this.recorder.stopRecording();
|
|
123
|
+
this.releaseStream();
|
|
124
|
+
this.recorder.dispose();
|
|
125
|
+
this.reset();
|
|
126
|
+
}
|
|
127
|
+
reset() {
|
|
128
|
+
this.chunks = [];
|
|
129
|
+
this.setState(MicState.Idle);
|
|
130
|
+
}
|
|
131
|
+
pauseStream() {
|
|
132
|
+
this.stream?.getTracks().forEach((track) => {
|
|
133
|
+
track.enabled = false;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
releaseStream() {
|
|
137
|
+
this.recorder.detachStream();
|
|
138
|
+
this.stream?.getTracks().forEach((track) => {
|
|
139
|
+
try {
|
|
140
|
+
track.stop();
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// ignore track shutdown failures during teardown
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
this.stream = undefined;
|
|
147
|
+
}
|
|
148
|
+
setState(state) {
|
|
149
|
+
this.state = state;
|
|
150
|
+
this.emit(MicEvents.StateChanged, { state });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function toError(error) {
|
|
154
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
155
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export declare enum RecorderEvents {
|
|
2
|
+
Start = "start",
|
|
3
|
+
Stop = "stop",
|
|
4
|
+
Data = "data",
|
|
5
|
+
Level = "level",
|
|
6
|
+
Error = "error"
|
|
7
|
+
}
|
|
8
|
+
type RecorderHandlerMap = {
|
|
9
|
+
[RecorderEvents.Start]: () => void;
|
|
10
|
+
[RecorderEvents.Stop]: () => void;
|
|
11
|
+
[RecorderEvents.Data]: (data: Blob) => void;
|
|
12
|
+
[RecorderEvents.Level]: (levels: number[]) => void;
|
|
13
|
+
[RecorderEvents.Error]: (error: Error) => void;
|
|
14
|
+
};
|
|
15
|
+
export declare class Recorder {
|
|
16
|
+
private audioContext?;
|
|
17
|
+
private mediaStream?;
|
|
18
|
+
private mediaStreamSource?;
|
|
19
|
+
private analyserNode?;
|
|
20
|
+
private recorderProcessorNode?;
|
|
21
|
+
private silentGainNode?;
|
|
22
|
+
private meterFrameId?;
|
|
23
|
+
private isRecording;
|
|
24
|
+
private setupPromise;
|
|
25
|
+
private listeners;
|
|
26
|
+
initialized: boolean;
|
|
27
|
+
on<K extends RecorderEvents>(event: K, handler: RecorderHandlerMap[K]): () => void;
|
|
28
|
+
private emit;
|
|
29
|
+
clearListeners(): void;
|
|
30
|
+
init(stream: MediaStream): Promise<void>;
|
|
31
|
+
ensureAudioGraph(): Promise<void>;
|
|
32
|
+
private setupAudioGraph;
|
|
33
|
+
private attachStream;
|
|
34
|
+
detachStream(options?: {
|
|
35
|
+
stopTracks?: boolean;
|
|
36
|
+
}): void;
|
|
37
|
+
startRecording(): Promise<void>;
|
|
38
|
+
stopRecording(): void;
|
|
39
|
+
dispose(): void;
|
|
40
|
+
get isRecordingStatus(): boolean;
|
|
41
|
+
private startMetering;
|
|
42
|
+
private stopMetering;
|
|
43
|
+
}
|
|
44
|
+
export {};
|